注解+AOP的运用(一)之模拟ORM框架(初版)

2019-03-29  本文已影响0人  一只在时光里流浪的大懒猫

了解了注解和AOP相关概念与技术实现之后,我们可以结合两者,做一些事情,如上文所说:
1.自定义简化版orm框架;
2.自定义简化版日志框架;
3.参数校验、过滤等。

这里,模拟orm框架。

整体思路:

自定义注解,在实体类上标注,告诉程序其所对应的表,对应的字段;在接口上标注,告诉程序该做什么操作(增删改查)。接口方法使用动态代理,解析方法上的注解,判断所要做的操作,根据操作不同,解析成对应的sql。解析sql,用到实体类上的注解,并结合上一步中方法上的注解。根据sql,执行对应的数据库操作。

大致设计:

在实体类上,我们需要告知程序对应的表、字段,那么就需要两个注解:@Table、@Column,同时,需要知道查询条件,那么还需要一个注解:@Query;在接口上,我们可以定义一个基础的接口,用来结合实体类上的注解,实现简单的增删改查,那么我们需要一个注解,来告知对应操作:@Operation;有时候,我们需要自己写sql,这个可以标注在方法上:@Sql。对于查询条件,可以放在实体类中,作为参数传入接口方法。

实现步骤:

1.自定义注解

Table 注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value();
}

Column 注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String value();
}

Query 注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
    String value() default "=";
    String expr() default "%-%";// 查询条件默认%-%匹配中间,%- 匹配结尾,-%匹配开头
}

注:查询条件拼接,默认 = ,可自行设置 =、>、<。设置为 like时,可设置匹配方式 expr。

Operation 注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Operation {
    OperationType value();
}

注:在Operation注解中,操作类型的值,使用枚举。

OperationType 枚举

public enum OperationType {
    SELECT,
    INSERT,
    UPDATE,
    DELETE
}

Sql 注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sql {
    String value();
    String[] params() default {};
}

注:value存储sql语句。若需要参数,语句中用?占位,params中存方法入参类的属性名,顺序对应sql中的?顺序。若不需要参数,则无需设置params。

2.实体类、接口标注注解

User.java 实体类标注

@Table("user")
public class User {

    @Query
    @Column("id")
    private int id;

//    @Query("like")
    @Column("name")
    private String name;

    @Column("phone")
    private String phone;

    private String address;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

BaseDao.java 基础接口标注

/**
 * 四个基础的语句,查询条件为实体类中所设置的Query
 *
 */
public interface BaseDao<T> {

    @Operation(OperationType.SELECT)
    List<T> selByEntity(T t);

    @Operation(OperationType.INSERT)
    int addEntity(T t);

    @Operation(OperationType.UPDATE)
    int updateEntity(T t);

    @Operation(OperationType.DELETE)
    int delEntity(T t);

}

注:基础接口使用泛型,传入类型参数。增加、更新、删除操作,返回影响记录数。

UserDao.java 接口标注

public interface UserDao extends BaseDao<User>{

    @Sql(value = "delete from user where id = ?",params = {"id"})
    void delUser(User user);

    @Sql(value = "insert into user (id,name,phone) values (?,'?','?')",params = {"id","name","phone"})
    void addUser(User user);

    @Sql(value = "update user set name = '?',phone = '?' where id = '?'",params = {"name","phone","id"})
    void updateUser(User user);

    @Sql(value = "select id,name,phone from user where name like '%?%'",params = {"name"})
    List<User> selUser(User user);

    // 测试无参数sql
    @Sql("select id,name,phone from user where name like '3%'")
    List<User> selUser3();

    @Sql( "delete from user where id = 999")
    void delUser3();
}

3.动态代理提供组装、执行sql的场地

这里使用动态代理,为接口生成代理类,从而使接口可进行调用。在代理方法中,获取并解析原接口方法上的注解,从而做出相应的动作。
MapperProxy.java 动态代理类

public class MapperProxy {


    public static <T> T getProxyInstance(Class<T> mapperClass){

        Object obj = null;

        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Object obj ;
                if (args != null){
                    // 基础接口方法中传入的参数,默认为一个对应的实体类
                    obj = args[0];
                }else {
                    // 如果方法没有入参,则去接口中寻找
                    // 获取接口中传入的实体类,并通过反射生成对象
                    Type[] type = mapperClass.getGenericInterfaces();
                    ParameterizedType parameterizedType = (ParameterizedType) type[0];
                    Type[] types = parameterizedType.getActualTypeArguments();
                    Class clazz = Class.forName(types[0].getTypeName());
                    obj = clazz.newInstance();
                }

                // result 为代理方法的返回结果,可将select语句的返回结果放入这里。其它语句返回 更新的记录数。
                Object result = null;
                try {

                    DBUtil dbUtil = new DBUtil();

                    // 这里拦截了所有方法。
                    // 获取方法上的注解,以此判断所要进行的操作。
                    Operation operation = method.getAnnotation(Operation.class);
                    if (operation != null){
                        if(operation.value() == OperationType.SELECT){
                            result = dbUtil.selByEntity(obj);
                        }else if(operation.value() == OperationType.UPDATE){
                            result = dbUtil.updateEntity(obj);
                        }else if(operation.value() == OperationType.INSERT){
                            result = dbUtil.addEntity(obj);
                        }else if(operation.value() == OperationType.DELETE){
                            result = dbUtil.delEntity(obj);
                        }
                    }

                    // 这里是自定义sql语句的方法
                    Sql sql = method.getAnnotation(Sql.class);
                    if (sql != null){
                        if (sql.value().startsWith("select")){
                            result = dbUtil.sqlExecuteSelect(obj,sql);
                        }else {
                            result = dbUtil.sqlExecute(obj,sql);
                        }
                    }

                }catch (Exception e){
                    e.printStackTrace();
                }

                return result;

            }
        };

        return  (T)Proxy.newProxyInstance(MapperProxy.class.getClassLoader(), new Class[]{mapperClass}, handler);
    }

}

注:getProxyInstance() 方法 接收泛型参数,从而返回对应dao类型。

4.工具类组装、执行sql

SqlUtil.java 组装sql

public class SqlUtil {

    public static String selectSQL(Object obj) throws Exception{

        StringBuffer sql = new StringBuffer();
        sql.append("select ");

        // 获取类上的注解
        Table table = obj.getClass().getAnnotation(Table.class);
        if (table != null) {
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field:fields){
                // 获取属性上的注解
                Column column = field.getAnnotation(Column.class);
                if(column != null){
                    sql.append(column.value()+",");
                }
            }
            sql.deleteCharAt(sql.length() - 1);
            sql.append(" from " + table.value());
            sql.append(sqlWhere(obj));
        }
        return sql.toString();
    }

    public static String insertSQL(Object obj) throws Exception{

        StringBuffer sql1 = new StringBuffer();
        sql1.append("insert into ");
        StringBuffer sql2 = new StringBuffer();
        sql2.append(" values (");

        // 获取类上的注解
        Table table = obj.getClass().getAnnotation(Table.class);
        if (table != null){
            sql1.append(table.value()+" ( ");

            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field:fields){
                Column column = field.getAnnotation(Column.class);
                if(column != null){
                    sql1.append(column.value()+",");
                    field.setAccessible(true);
                    if(field.getGenericType().toString().equals("class java.lang.String")){
                        sql2.append("'" + field.get(obj)+"',");
                    }else {
                        sql2.append(field.get(obj)+",");
                    }
                }
            }

            sql1.deleteCharAt(sql1.length() - 1);
            sql1.append(") ");

            sql2.deleteCharAt(sql2.length() - 1);
            sql2.append(") ");

            sql1.append(sql2);
        }

        return sql1.toString();
    }

    public static String updateSQL(Object obj) throws Exception{
        
        StringBuffer sql = new StringBuffer();
        sql.append("update ");

        // 获取类上的注解
        Table table = obj.getClass().getAnnotation(Table.class);
        if (table != null){
            sql.append(table.value()+" set ");
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field:fields) {
                // 获取属性上的注解
                Column column = field.getAnnotation(Column.class);
                if (column != null) {
                    field.setAccessible(true);
                    if(field.getGenericType().toString().equals("class java.lang.String")){
                        // 若属性为String ,组装时则需要 ''
                        sql.append(column.value()+ "= '" + field.get(obj) + "' ,");
                    }else {
                        sql.append(column.value()+ "= " + field.get(obj) + " ,");
                    }
                }
            }
            sql.deleteCharAt(sql.length() - 1);
            // 组装 where
            sql.append(sqlWhere(obj));
        }
        return sql.toString();
    }

    public static String deleteSQL(Object obj) throws Exception{
        StringBuffer sql = new StringBuffer();
        sql.append("delete from ");
        Table table = obj.getClass().getAnnotation(Table.class);
        if (table != null){
            sql.append(table.value());
            sql.append(sqlWhere(obj));
        }
        return sql.toString();
    }

    public static String sqlSQL(Object obj,Sql sql) throws Exception{

        // 若 params 为空,则不需要拼接参数,直接返回Sql注解上的value值
        String[] params = sql.params();
        if (params.length == 0){
            return sql.value();
        }

        // 1.将params中的属性名替换成值
        Object[] pars = new Object[params.length];
        for (int i = 0;i<params.length;i++){
            Field[] fields = obj.getClass().getDeclaredFields();
            for (Field field : fields){
                Column column = field.getAnnotation(Column.class);
                if (column != null){
                    if(params[i].equals(column.value())){
                        field.setAccessible(true);
                        pars[i] = field.get(obj);
                    }
                }
            }
        }

        // 2.用params替换sql中的?
        StringBuilder stringBuilder = new StringBuilder(sql.value());
        for (int i = 0;i<params.length;i++){
            stringBuilder.replace(stringBuilder.indexOf("?"),stringBuilder.indexOf("?")+1,String.valueOf(pars[i]));
        }

        return stringBuilder.toString();
    }

    public static StringBuffer sqlWhere(Object obj) throws Exception{
        StringBuffer sqlWhere = new StringBuffer();
        sqlWhere.append(" where 1=1 ");
        Field[] fields = obj.getClass().getDeclaredFields();
        for (Field field:fields) {
            Query query = field.getAnnotation(Query.class);
            field.setAccessible(true);
            if(query != null && field.get(obj) != null){
                if ("like".equals(query.value())){
                    // like 的匹配规则
                    String expr = query.expr();
                    if ("%-%".equals(expr)){
                        // %-%匹配中间
                        sqlWhere.append(" and "+field.getName()+" "+query.value()+" '%"+field.get(obj)+"%'");

                    }else if("%-".equals(expr)){
                        // %- 匹配结尾
                        sqlWhere.append(" and "+field.getName()+" "+query.value()+" '%"+field.get(obj)+"'");

                    }else if("-%".equals(expr)){
                        // -% 匹配开头
                        sqlWhere.append(" and "+field.getName()+" "+query.value()+" '"+field.get(obj)+"%'");

                    }
                }else {
                    sqlWhere.append(" and "+field.getName()+" "+query.value()+" '"+field.get(obj)+"'");

                }
            }
        }
        return sqlWhere;
    }
}

DBUtil.java 执行sql

public class DBUtil {

    private static String driverName = "com.mysql.cj.jdbc.Driver";
    private static String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false";
    private static String username = "root";
    private static String password = "root";

    /**
     * 获取数据库链接
     * @param driverName
     * @param url
     * @param username
     * @param password
     * @return
     */
    public static Connection getConn(String driverName, String url, String username, String password) {
        Connection conn = null;
        try {
            // 加载驱动
            Class.forName(driverName);
            conn = DriverManager.getConnection(url, username, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 执行 update、insert、delete语句,返回影响记录数
     * @param sql
     * @return
     * @throws Exception
     */
    public int execute(String sql) throws Exception{
        int count = 0;
        Connection conn = null;
        try {
            conn = getConn(driverName, url, username, password);
            // 设置事务不自动提交
            conn.setAutoCommit(false);
            // 回滚点
            conn.setSavepoint();

            PreparedStatement pst;
            pst = conn.prepareStatement(sql);
            count = pst.executeUpdate();

            conn.commit();// 提交
        }catch (Exception e){
            e.printStackTrace();
        }
        return count;
    }

    /**
     *  执行 select 语句,返回list
     * @param obj
     * @param sql
     * @return
     * @throws Exception
     */
    public List<Object> select(Object obj,String sql) throws Exception{

        List<Object> list = new ArrayList<Object>();

        Connection conn = null;
        try {
            conn = getConn(driverName,url,username,password);
            // 设置事务不自动提交
            conn.setAutoCommit(false);
            // 回滚点
            conn.setSavepoint();

            PreparedStatement pst;
            ResultSet rSet;

            pst = conn.prepareStatement(sql);
            rSet = pst.executeQuery();

            Class clz = obj.getClass();

            // 遍历结果
            while (rSet.next()) {
                Object item = clz.newInstance();
                Field[] fields = obj.getClass().getDeclaredFields();
                for (Field field : fields){
                    Column column = field.getAnnotation(Column.class);
                    if (column != null){
                        field.setAccessible(true);
                        // 获取数据库值,并匹配设置属性
                        field.set(item,rSet.getObject(column.value()));
                    }
                }
                list.add(item);
            }

            conn.commit();// 提交
        }catch (Exception e){
            e.printStackTrace();
        }

        return list;

    }

    public List<Object> selByEntity(Object obj) throws Exception{
        return select(obj,SqlUtil.selectSQL(obj));
    }

    public int addEntity(Object obj) throws Exception{
        return execute(SqlUtil.insertSQL(obj));
    }

    public int updateEntity(Object obj) throws Exception{
       return execute(SqlUtil.updateSQL(obj));
    }

    public int delEntity(Object obj) throws Exception{
        return execute(SqlUtil.deleteSQL(obj));
    }

    public List<Object> sqlExecuteSelect(Object obj,Sql s) throws Exception{
        return select(obj,SqlUtil.sqlSQL(obj,s));
    }

    public int sqlExecute(Object obj,Sql s) throws Exception {
        return execute(SqlUtil.sqlSQL(obj,s));
    }

}

注:这里每次执行一次数据库操作,都会去连接一次数据库,非常不合理。后续文章中,对这个orm进行优化,增加数据库连接池。对于数据库信息,也做可配置化,放入db.properties中。

5.应用示例

    public static void main(String[] args) {

        /* */
        User user = new User();
        user.setId(1);
        user.setName("nep");
        user.setPhone("123456");

        UserDao userDao = MapperProxy.getProxyInstance(UserDao.class);

        Object obj = null;
//        obj = userDao.addEntity(user);
//        obj = userDao.updateEntity(user);
//        obj = userDao.delEntity(user);
//        obj = userDao.selByEntity(user);
        System.out.println(JSON.toJSONString(obj));
    }

至此,一个简易版的ORM完成了,对于注解结合AOP思想(这里用了动态代理),有了一定的实践。不过,这里仍有许多不足之处,如缺少数据库连接池、基础sql的拼接中参数不灵活、事务没有分开,无法进行多条sql的统一事务处理、缺少批量更新等,后续文章进行持续优化。

附录:
源码下载:
链接:https://pan.baidu.com/s/1B9kJWKP6QlmEYm-YR1B0fw
提取码:7z3a

上一篇下一篇

猜你喜欢

热点阅读