用反射手写mybatis全注解实现(反射真实应用)

反射手写 mybatis 全注解实现(反射真实应用)

为了简化代码部分操作省略,只为突出反射思想和概念


Q & A


Q

  1. mybatis为我们做了哪些事?
  2. 配置文件有何作用?
  3. 为什么用到反射?

A

  1.  mybatis 讲我们的查询结果自动封装到实体类。
  2. 配置文件可以解耦,我们想把mysql驱动换成oracle驱动只需要修改配置文件即可。
  3. 由于我们不知道用到哪个实体类封装数据,所以这个过程是动态的。我们需要动态获取类信息,进行动态封装数据。

实现

  • 编写数据库配置文件(jdbc.properties),读取配置文件工具类(PropertiesUtil)

  • 编写自定义注解(SelectAnnotation)

  • 编写数据库连接类(JDBCUtil)

  • 编写dao层(StudentDao)

  • 编写处理注解的类(重点实现)(SelectAnnotation2)

  • 编写测试类(Test)


编写数据库配置文件(jdbc.properties),读取配置文件工具类(PropertiesUtil)


​ 编写数据库配置文件(jdbc.properties)数据库用的是 MySQL8 版本,连接驱动 url 有所改变

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

​ 编写读取配置文件工具类(PropertiesUtil),properties数据为key-value关系储存。

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);
    }
}

编写自定义注解(SelectAnnotation)


​ 此处编写一个简单的自定义注解

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

编写数据库连接类(JDBCUtil)


​ 此处编写一个比较水的数据库连接以及查询结果

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;
    }

}

编写dao层(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");
    }
}

编写处理注解的类(重点实现)(SelectAnnotation2)


此处如果反射基础不好,请看另一篇文章

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;
    }

}

编写测试类(Test)

​ 简单测试 一下数据是否读取成功

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());
    }
}

附录(数据库文件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');

完结!撒花!

猜你喜欢

转载自blog.csdn.net/qq_44112474/article/details/108529628
今日推荐