反射手写 mybatis 全注解实现(反射真实应用)
为了简化代码部分操作省略,只为突出反射思想和概念
Q & A
Q
- mybatis为我们做了哪些事?
- 配置文件有何作用?
- 为什么用到反射?
A
- mybatis 讲我们的查询结果自动封装到实体类。
- 配置文件可以解耦,我们想把mysql驱动换成oracle驱动只需要修改配置文件即可。
- 由于我们不知道用到哪个实体类封装数据,所以这个过程是动态的。我们需要动态获取类信息,进行动态封装数据。
实现
-
编写数据库配置文件(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');