仿Mybatis手撸注解SQL实现DAO接口(下)——动态代理的使用

需求

上一篇我们对JDBC进行了简单的封装,主要实现了对结果集和java bean的映射。现在我们需要通过代理调用封装好的DBUtils动态实现DAO接口

定义增删改查注解@Insert, @Delete, @Update, @Select

以下四个注解

  • @Target(ElementType.METHOD) 指定注解的作用目标为Method
  • @Retention(RetentionPolicy.RUNTIME) 指定注解的作用域为Runtime,即可
    以在JVM通过反射获取该注解的信息
  • 四个注解都有一个value属性,用于保存sql
// Insert.java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Insert { String value(); } // Delete.java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Delete { String value(); } // Update.java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Update { String value(); } // Select.java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Select { String value(); }

通过动态代理实现接口

  • Java动态代理 Proxy.newProxyInstance(Interface, Class[],InvocationHandler)
  • 自定义SQLInvocationHandler来实现对标准过SQL注解的Method解析并调用相应DBUtils的方法来实现接口方法
    • 使用@Select的接口,首先判断其返回类型是否存在泛型(说明肯定不是基本类型)
      • 如果存在泛型,目前只支持返回List,调用DBUtils.selectList()方法实现
      • 如果不存在泛型,判断是否为基本类型,如果是基本类型,说明查询的是单列,调用DButils.selectValue()实现。如果不是基本类型,调用DBUtils.selectOne()方法实现
    • 使用@Insert, @Update, @Delete的接口,调用DBUtils.execute()方法实现
public class ProxyFactory { public static Object proxyMapper(Class<?>... clazz) { return Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), clazz, new SQLInvocationHandler()); } // 代理接口 private static class SQLInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { DBUtils dbUtils = DBUtils.getInstance(); Connection connection = dbUtils.getConnection(); // 检查Select注解 if (method.isAnnotationPresent(Select.class)) { Select one = method.getAnnotation(Select.class); String sql = one.value(); Type returnType = method.getGenericReturnType(); // 判断返回类型是否存在泛型 if (returnType instanceof ParameterizedType) { ParameterizedType actualType = (ParameterizedType) returnType; // 集合支持List if (actualType.getRawType().equals(List.class)) { // 获取泛型的类型 Class<?> mapperType = (Class<?>)actualType.getActualTypeArguments()[0]; return dbUtils.selectList(connection, mapperType, sql, args); } throw new RuntimeException("Unsupported Return Type: " + method.toString()); } // 不存在泛型 Class<?> clazz = (Class<?>)returnType; if (ReflectUtil.isPrimaryType(clazz)){ // 基本类型 return dbUtils.selectValue(connection, clazz, sql, args); } else { return dbUtils.selectOne(connection, clazz, sql, args); } } else if (method.isAnnotationPresent(Update.class)) { // 处理Update Update update = method.getAnnotation(Update.class); return dbUtils.execute(connection, update.value(), args); } else if (method.isAnnotationPresent(Delete.class)) { // 处理Delete Delete delete = method.getAnnotation(Delete.class); return dbUtils.execute(connection, delete.value(), args); } else if (method.isAnnotationPresent(Insert.class)) { // 处理Insert Insert insert = method.getAnnotation(Insert.class); return dbUtils.execute(connection, insert.value(), args); } return null; } } }

使用ProxyFactory动态代理一个DAO接口

  1. 准备数据
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `test` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; USE `test`; DROP TABLE IF EXISTS `t_student`; CREATE TABLE `t_student` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key', `created_time` datetime DEFAULT NULL COMMENT 'created time', `updated_time` datetime DEFAULT NULL COMMENT 'updated time', `name` varchar(20) DEFAULT NULL COMMENT '姓名', `birthday` date DEFAULT NULL COMMENT '生日', `address` varchar(200) DEFAULT NULL COMMENT '家庭住址', `phone` varchar(20) DEFAULT NULL COMMENT '联系方式', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 COMMENT='学生信息表'; INSERT INTO `t_student` (`id`,`created_time`,`updated_time`,`name`,`birthday`,`address`,`phone`) VALUES (2,'2020-11-03 11:43:06','2020-11-03 11:43:06','李自成','2000-05-01','广东韶关','177888888'), (3,'2020-11-03 11:43:06','2020-11-03 11:43:06','王老五','2002-03-01','江苏南京','199888888'), (4,'2020-11-03 11:43:06','2020-11-03 11:43:06','葛二蛋','2000-12-01','四川成都','166888888'), (5,'2020-11-03 02:15:31','2020-11-03 02:15:31','赵四','2000-03-01','新疆乌鲁木齐','166666666'), (6,'2020-11-25 17:17:44','2020-11-25 17:17:44','???','2020-11-25','????','13333333333'), (7,'2020-11-25 17:20:18','2020-11-25 17:20:18','牛夫人','2020-11-25','四川成都','13333333333'), (8,'2020-11-26 10:42:31','2020-11-26 10:42:31','蒋某','2020-11-26','广东东莞','1234322112');
  1. 对应t_student表的Java bean
package com.yeyeck.yebatis.test; import java.time.LocalDate; import java.time.LocalDateTime; import lombok.Data; @Data public class Student { private Integer id; private String name; private LocalDate birthday; private String address; private String phone; private LocalDateTime createdTime; private LocalDateTime updatedTime; }
  1. HikariCP 配置
jdbcUrl=jdbc:mysql://localhost:3306/test?characterEncoding=utf8 username=root password=password dataSource.cachePrepStmts=true dataSource.prepStmtCacheSize=250 dataSource.prepStmtCacheSqlLimit=2048 dataSource.useServerPrepStmts=true dataSource.useLocalSessionState=true dataSource.rewriteBatchedStatements=true dataSource.cacheResultSetMetadata=true dataSource.cacheServerConfiguration=true dataSource.elideSetAutoCommits=true dataSource.maintainTimeStats=false
  1. 定义接口StudentDao
import java.time.LocalDate; import java.util.List; import com.yeyeck.yebatis.annotation.Delete; import com.yeyeck.yebatis.annotation.Insert; import com.yeyeck.yebatis.annotation.Select; import com.yeyeck.yebatis.annotation.Update; public interface StudentDao { @Select("select * from t_student where id = ?") Student findById(Integer id); @Select("select * from t_student") List<Student> findAll(); @Select("select count(id) from t_student") Integer countAll(); @Update("update t_student set name = ? where id = ?") int updateName(String name, Integer id); @Insert("insert into t_student(name, birthday, address, phone, created_time, updated_time)" + "values (?, ?, ?, ?, now(), now())") int addStudent(String name, LocalDate birthday, String address, String phone); @Delete("delete from t_student where id = ?") int removeById(Integer id); @Select("select birthday from t_student where id = ?") LocalDate findBirthdayById(Integer id); @Select("select name from t_student") List<String> findAllNames(); }
  1. 使用动态代理实现StudentDao接口
// 代理接口 StudentDao studentDao = (StudentDao)ProxyFactory.proxyMapper(StudentDao.class); // 使用接口 List<Student> students = studentDao.findAll(); System.out.println(students);
阅读(125)
评论(0)
updated@2020-11-26
评论区
目录