美文网首页
注解+AOP的运用(一)之模拟ORM框架(初版)

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

作者: 一只在时光里流浪的大懒猫 | 来源:发表于2019-03-29 15:24 被阅读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

    相关文章

      网友评论

          本文标题:注解+AOP的运用(一)之模拟ORM框架(初版)

          本文链接:https://www.haomeiwen.com/subject/ewbhvqtx.html