Handwritten mybatis full annotation implementation with reflection (reflection real application)

Realization of reflection handwritten mybatis full annotation (reflection real application)

In order to simplify the operation of the code part, it is only to highlight reflection ideas and concepts


Q & A


Q

  1. What does mybatis do for us?
  2. What does the configuration file do?
  3. Why is reflection used?

A

  1.  Mybatis says that our query results are automatically encapsulated into entity classes.
  2. The configuration file can be decoupled. If we want to change the mysql driver to the oracle driver, we only need to modify the configuration file.
  3. Since we don't know which entity class is used to encapsulate the data, this process is dynamic. We need to dynamically obtain class information and dynamically encapsulate data.

achieve

  • Write database configuration file (jdbc.properties), read configuration file tool class (PropertiesUtil)

  • Write custom annotations (SelectAnnotation)

  • Write database connection class (JDBCUtil)

  • Write dao layer (StudentDao)

  • Write a class that handles annotations ( key implementation ) (SelectAnnotation2)

  • Write a test class (Test)


Write database configuration file (jdbc.properties), read configuration file tool class (PropertiesUtil)


​ The database configuration file (jdbc.properties) is written using MySQL8 version, and the connection driver url has changed

driverName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mytest?serverTimezone=Asia/Shanghai&useSSL=false&autoReconnect=true&characterEncoding=utf-8
userName=root
password=123456

​ Write a tool class for reading configuration files (PropertiesUtil), and the properties data is stored in a key-value relationship.

public class PropertiesUtil {
    
    

    /**
     * 读取配置文件
     * @param key 配置文件中的key
     * @return String 配置文件中key对应的value
     * @throws IOException 异常
     */
    public static String getValue(String key) throws IOException {
    
    
        Properties properties = new Properties();
        // 文件名自定义
        FileReader fileReader = new FileReader("jdbc.properties");
        properties.load(fileReader);
        fileReader.close();
        // 在properties文件中的信息是key-value关系
        return  properties.getProperty(key);
    }
}

Write custom annotations (SelectAnnotation)


​ Write a simple custom annotation here

/**
 * @Decription: 模仿mybatis中@Select注解
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.METHOD,ElementType.TYPE})
public @interface SelectAnnotation {
    
    
    String value();
}

Write database connection class (JDBCUtil)


​ Write a database connection and query results that compare water here

public class JDBCUtil {
    
    

    // 此处的修饰词不能为final,因为String不可修改。如果用final,引用不会改变。
    private static String url = "";
    private static String userName = "";
    private static String password = "";

    static {
    
    
        try {
    
    
            // 从配置文件读取驱动
            Class.forName(PropertiesUtil.getValue("driverName"));
        } catch (ClassNotFoundException e1) {
    
    
            e1.printStackTrace();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
    
    
        return DriverManager.getConnection(url, userName, password);
    }


    public static PreparedStatement getPs(String sql) {
    
    
        PreparedStatement psmt = null;
        try {
    
    
            psmt = getConnection().prepareStatement(sql);
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
        return psmt;
    }

    public static ResultSet getResultSet(String sql) {
    
    
        PreparedStatement psmt = getPs(sql);
        try {
    
    
            return psmt.executeQuery();
        } catch (SQLException throwables) {
    
    
            throwables.printStackTrace();
        }
        return null;
    }

}

Write dao layer (StudentDao)


public class StudentDao {
    
    

    // 自定义注解
    @SelectAnnotation("select id,name,age from student")
    public List<Student> findAll() throws NoSuchMethodException, IllegalAccessException, InstantiationException, SQLException, InvocationTargetException, ClassNotFoundException, NoSuchFieldException, IOException {
    
    
        // 模仿框架调用处理类
        return SelectAnnotationHandle2.selectHandle(StudentDao.class, "findAll");
    }
}

Write a class that handles annotations ( key implementation ) (SelectAnnotation2)


If the reflection foundation is not good here, please see another article

public class SelectAnnotationHandle2 {
    
    

    /**
     * 根据注解所在的类和方法,返回sql执行结果
     *
     * @param cls        dao的类名
     * @param methodName 注解的方法名
     * @return List 数据库集合
     * @throws NoSuchMethodException
     * @throws SQLException
     * @throws ClassNotFoundException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchFieldException
     * @throws IOException
     */
    public static List selectHandle(Class cls, String methodName) throws NoSuchMethodException, SQLException, ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException, IOException {
    
    
        // 用于接收返回值
        List list = new ArrayList();
        // 模仿框架获取用户定义扫描实体包
        String scanEntity = "edu.xja.diy.entity.";
        // 获得注解
        Method method = cls.getDeclaredMethod(methodName);
        // 查看方法是否有该注解
        boolean exist = method.isAnnotationPresent(SelectAnnotation.class);
        if (exist) {
    
    
            // 获得方法上自定义的注解
            SelectAnnotation annotation = method.getAnnotation(SelectAnnotation.class);
            // 获得注解内容,内容为sql语句
            String sql = annotation.value();
            // 通过反射读取配置文件,注射配置文件到数据库连接工具类
            Class jdbcClass = Class.forName("edu.xja.diy.util.JDBCUtil");
            // 注入url
            Field url = jdbcClass.getDeclaredField("url");
            // 放开权限检查
            url.setAccessible(true);
            // 由于是静态字段,所以set第一个参数为null,第二个参数为配置文件的value
            url.set(null, PropertiesUtil.getValue("url"));
            // 注入userName
            Field userName = jdbcClass.getDeclaredField("userName");
            userName.setAccessible(true);
            userName.set(null, PropertiesUtil.getValue("userName"));
            // 注入password
            Field password = jdbcClass.getDeclaredField("password");
            password.setAccessible(true);
            password.set(null, PropertiesUtil.getValue("password"));
            // 获得sql执行结果
            ResultSet resultSet = JDBCUtil.getResultSet(sql);
            while (resultSet.next()) {
    
    
                // 获得结果集的元数据结构
                ResultSetMetaData metaData = resultSet.getMetaData();
                // 获得实体类class
                Class entityClass = null;
                // 获得实体类对象
                Object object = null;
                // metaData.getColumnCount()为元数据条数
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
    
    
                    // 获取列名
                    String columnName = metaData.getColumnName(i);
                    // 如果返回值的对象还没创建,则初始化
                    if (entityClass == null) {
    
    
                        // 该处体验下双检查锁
                        synchronized (SelectAnnotationHandle2.class) {
    
    
                            if (entityClass == null) {
    
    
                                // 获得元数据表名,查询不能嵌套查询,只能简单查询
                                String tableName = metaData.getTableName(i);
                                // 获得全限定类名
                                String AllEntityName = scanEntity + upFirstCase(tableName);
                                // 根据全限定类名获得实体类
                                entityClass = Class.forName(AllEntityName);
                                // 初始化一个对象
                                object = entityClass.getDeclaredConstructor().newInstance();
                            }
                        }
                    }
                    // 字段数据类型,例如id字段的数据类型为什么
                    Class dataType = null;
                    // 数据类型匹配
                    if (metaData.getColumnType(i) == Types.INTEGER) {
    
    
                        dataType = Integer.class;
                    }
                    if (metaData.getColumnType(i) == Types.VARCHAR) {
    
    
                        dataType = String.class;
                    }
                    // 获得对应属性的set方法,例如setId
                    Method setMethod = entityClass.getDeclaredMethod("set" + upFirstCase(columnName), dataType);
                    // 执行set注入
                    setMethod.invoke(object, resultSet.getObject(i));
                }
                // 循环添加到返回的结果集中
                list.add(entityClass);
            }
        }
        return list;

    }

    /**
     * 首字母转大写
     *
     * @param string 入参字符串
     * @return String
     */
    private static String upFirstCase(String string) {
    
    
        if (string.length() > 0) {
    
    
            string = string.substring(0, 1).toUpperCase() + string.substring(1);
        }
        return string;
    }

}

Write a test class (Test)

​ Simply test whether the data is successfully read

public class Test1 {
    
    
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, SQLException, InvocationTargetException, ClassNotFoundException, NoSuchFieldException, IOException {
    
    
        // 测试类
        StudentDao studentDao = new StudentDao();
        List<Student> all = studentDao.findAll();
        System.out.println("all = " + all.size());
    }
}

Appendix (database file mysql8)

/*
Navicat MySQL Data Transfer

Source Server         : mysql8
Source Server Version : 80018
Source Host           : localhost:3306
Source Database       : mytest

Target Server Type    : MYSQL
Target Server Version : 80018
File Encoding         : 65001

Date: 2020-09-11 10:39:29
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `age` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', 'zs', '12');
INSERT INTO `student` VALUES ('2', 'ls', '18');

end! Sprinkle flowers!

Guess you like

Origin blog.csdn.net/qq_44112474/article/details/108529628