1.动态代理机制
基于InvocationHandler实现的动态代理(不了解的同学建议先去学习一下动态代理)
1.1大致步骤
1.获取配置文件config.xml
2.通过JDBC执行SQL,并通过开发者自定义的接口,生成动态代理对象
3.创建实现类
2.MyBatis的具体实现
1.项目结构
Test为主类
Book为对应数据库表的实体类
pom.xml文件在这里展示
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>mymybatis001</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
注意:plugins 那里可能会报红 可忽略
2.用到的数据库
MySQL+ DataGrip
实体表:
这里要注意bookid要设置为自增
3.1主类Test
package com.aliano.test;
import com.aliano.entity.Book;
import com.aliano.mapper.BookMapper;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
MyBatisInvocationHandler handler = new MyBatisInvocationHandler();
BookMapper mapper = (BookMapper) handler.bind(BookMapper.class);
List<Book> list = mapper.list();
for (Book book : list) {
System.out.println(book);
}
}
}
3.2工具类MyBatisUtils
package com.aliano.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MyBatisUtil {
//定义数据库连接池
private static ComboPooledDataSource dataSource = null;
public static Connection getConnection(){
Connection connection = null;
try {
//读取XML,获取配置信息
Map<String, String> map = parseXML();
//加载数据源
loadDataSource(map);
connection = dataSource.getConnection();
} catch (Exception throwables) {
throwables.printStackTrace();
}
return connection;
}
public static void release(Connection connection, Statement statement, ResultSet resultSet){
try {
if(connection != null) connection.close();
if(statement != null) statement.close();
if(resultSet != null) resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public static void loadDataSource(Map<String,String> map){
try {
dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(map.get("driver"));
dataSource.setJdbcUrl(map.get("url"));
dataSource.setUser(map.get("username"));
dataSource.setPassword(map.get("password"));
dataSource.setInitialPoolSize(5);
dataSource.setMaxPoolSize(10);
dataSource.setMinPoolSize(3);
dataSource.setAcquireIncrement(5);
} catch (PropertyVetoException e) {
e.printStackTrace();
}
}
public static Map<String,String> parseXML() throws Exception {
Map<String,String> map = new HashMap<String, String>();
//读取config.xml
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("src/main/resources/config.xml");
Element rootElement = document.getRootElement();
Iterator<Element> iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
Element next = iterator.next();
if(next.getName().equals("environments")){
Iterator<Element> iterator1 = next.elementIterator();
while (iterator1.hasNext()) {
Element next1 = iterator1.next();
Iterator<Element> iterator2 = next1.elementIterator();
while (iterator2.hasNext()) {
Element next2 = iterator2.next();
if("dataSource".equals(next2.getName())){
Iterator<Element> iterator3 = next2.elementIterator();
while (iterator3.hasNext()) {
Element next3 = iterator3.next();
String name = next3.attributeValue("name");
String value = next3.attributeValue("value");
map.put(name, value);
}
}
}
}
}
}
return map;
}
}
这一块是难点所在 既有JDBC连接的实现还有配置文件的读取。
步骤是先读取项目xml文件里的配置, 即:
1.用parseXML获取config.xml文件的配置
config.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- MyBatis环境 -->
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test1"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/aliano/mapper/BookMapper.xml"/>
</mappers>
</configuration>
由于这一段代码特别乱,建议初学者使用Dubug打断点的方式去理解。
这里是过程:
接着就是对数据库连接池以及JDBC的配置:
3.3动态代理的实现类 MyBatisInvocationHandler
package com.aliano.test;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MyBatisInvocationHandler implements InvocationHandler {
public Object bind(Class cls){
return Proxy.newProxyInstance(
MyBatisInvocationHandler.class.getClassLoader(),
new Class[]{cls},
this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取数据源
List list = new ArrayList();
Connection connection = MyBatisUtil.getConnection();
PreparedStatement statement = null;
ResultSet resultSet = null;
SAXReader reader = new SAXReader();
Document document = reader.read("src/main/java/com/aliano/mapper/BookMapper.xml");
Element rootElement = document.getRootElement();
Iterator<Element> iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
Element next = iterator.next();
String name = method.getName();
if (next.attributeValue("id").equals(name)) {
String sql = next.getText();
statement = connection.prepareStatement(sql);
resultSet = statement.executeQuery();
String resultType = next.attributeValue("resultType");
//通过反射机制获取目标类
Class<?> aClass = Class.forName(resultType);
Field[] declaredFields = aClass.getDeclaredFields();
while (resultSet.next()) {
Object instance = aClass.getConstructor(null).newInstance(null);
for (Field declaredField : declaredFields) {
Object value = null;
String methodName = "set"+declaredField.getName().substring(0, 1).toUpperCase()+declaredField.getName().substring(1);
Method declaredMethod = aClass.getDeclaredMethod(methodName, declaredField.getType());
switch (declaredField.getType().getName()){
case "java.lang.Integer":
value = resultSet.getInt(declaredField.getName());
declaredMethod.invoke(instance,(Integer)value);
break;
case "java.lang.String":
value = resultSet.getString(declaredField.getName());
declaredMethod.invoke(instance,(String)value);
break;
case "java.lang.Double":
value = resultSet.getDouble(declaredField.getName());
declaredMethod.invoke(instance,(Double)value);
break;
}
}
list.add(instance);
}
MyBatisUtil.release(connection, statement, resultSet);
}
}
return list;
}
}
List用来记录最后的返回对象。
这里接下里继续看三个文件:
Book 与数据表对应
package com.aliano.entity;
import lombok.Data;
@Data
public class Book {
private Integer bookid;
private String name;
private Double price;
}
BookMapper.java 定义增删改查实现的接口 为反射准备
package com.aliano.mapper;
import com.aliano.entity.Book;
import java.util.List;
public interface BookMapper {
public List<Book> list();
public Book findById(Integer id);
public void add(Book book);
public void update(Book book);
public void delete(Integer id);
}
BookMapper.xml 定义SQL语句的地方
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aliano.mapper.BookMapper">
<insert id="add">
insert into book(name,price) values(#{name},#{price})
</insert>
<update id="update">
update book set name = #{name},price = #{price} where bookid = #{bookid}
</update>
<delete id="delete">
delete from book where bookid = #{id}
</delete>
<select id="list" resultType="com.aliano.entity.Book">
select * from book
</select>
<select id="findById" resultType="com.aliano.entity.Book">
select * from book where bookid = #{id}
</select>
</mapper>
回到反射类
这里是利用传过来的接口 即Test类里的
MyBatisInvocationHandler handler = new MyBatisInvocationHandler();
BookMapper mapper = (BookMapper) handler.bind(BookMapper.class);
动态创建MyBatis的代理对象 参数是运行时类 从而获取方法 属性 以及类的参数等。
初始化JDBC的工作
这里我们以list方法为例
这一块同样是利用SAXReader来获取xml文件的内容,觉得比较乱的同学建议打断点去梳理。
紧接着就是通过反射机制来动态的获取本次调用方法的目标类。
通过resultType我们可以得知本次获取到的运行时类包括com.aliano.entity.Book
使用反射机制获取Book类里面的三个属性,记为declaredFields
Object instance = aClass.getConstructor(null).newInstance(null);
对当前Book类实现无参构造,实例化一个instance(即Book对象的实例化)
for (Field declaredField : declaredFields) {
Object value = null;
String methodName = "set"+declaredField.getName().substring(0, 1).toUpperCase()+declaredField.getName().substring(1);
Method declaredMethod = aClass.getDeclaredMethod(methodName, declaredField.getType());
switch (declaredField.getType().getName()){
case "java.lang.Integer":
value = resultSet.getInt(declaredField.getName());
declaredMethod.invoke(instance,(Integer)value);
break;
case "java.lang.String":
value = resultSet.getString(declaredField.getName());
declaredMethod.invoke(instance,(String)value);
break;
case "java.lang.Double":
value = resultSet.getDouble(declaredField.getName());
declaredMethod.invoke(instance,(Double)value);
break;
}
}
这里是通过属性名来动态生成Setter 和 Getter 方法名 以及属性的类型
这里举例是Integer、String、Double(实际上肯定还有更多)
然后进switch:
value = resultSet.getInt(declaredField.getName());
value = resultSet.getString(declaredField.getName());
value = resultSet.getDouble(declaredField.getName());
分别获取结果集中bookid name price 对应的值 用value存储
declaredMethod.invoke(instance,(Integer)value);
declaredMethod.invoke(instance,(String)value);
declaredMethod.invoke(instance,(Double)value);
利用反射机制 实现三个值的set方法,完成赋值,最后通过for循环 依次将instance实例对象添加到list
list.add(instance);
最后不要忘记释放JDBC连接资源
MyBatisUtil.release(connection, statement, resultSet);
最后 返回主类
遍历得到结果:
3.总结
本文所用到的主要技术就是JDBC的实现,以及通过反射机制实现的动态代理。也算是进行一定程度的复习。
紧接着才是一些流程的封装和调用,整个动态的过程都离不开动态代理这一设计模式。需要对流程完成一定的梳理,然后进行粗实现,再去考虑代码的封装性,可复用性以及耦合性。
所有优秀的框架都离不开底层知识以及核心机制,MyBatis和Spring等优秀框架都是站在巨人的肩膀上完成的,没有什么真正难的框架,都是基于底层知识一层一层的叠加,一层一层的封装。一点一点的的积累吧。