需求
上一篇我们对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()方法实现
- 使用@Select的接口,首先判断其返回类型是否存在泛型(说明肯定不是基本类型)
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接口
- 准备数据
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');
- 对应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;
}
- 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
- 定义接口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();
}
- 使用动态代理实现StudentDao接口
// 代理接口
StudentDao studentDao = (StudentDao)ProxyFactory.proxyMapper(StudentDao.class);
// 使用接口
List<Student> students = studentDao.findAll();
System.out.println(students);