Mybatis自定义接口底层实现的方式
1. 创建Dao层的自定义接口
首先创建Dao层数据库的接口,为了理解Mybatis动态代理机制,简单的以下述方法为例进行操作
package mymybatis;
import com.cqupt.entity.User;
import java.util.List;
public interface UserMapper {
public User findById(Integer id);
public List<User> findAll();
}
2. 在对应的XML 中定义接口方法对应的 SQL 语句
第二步,创建与之对应的xml,注意<mapper namespace="mymybatis.UserMapper"中的命名空间在mybatis中需要设置为接口的全类名,使得mybatis在创建动态代理的时候,能够通过全类名找到对应的xml来正确的生成其代理对象实例,这也就是为什么需要在mybatis的配置文件中也需要注册自己写的mapper.xml文件。
接口通过接口的全类名+方法名如 mymybatis.UserMapper+findById ,Mybatis首先会查到到对应的命名空间,然后在其命名空间下通过接口中方法名与<selecte标签中的id进行匹配找到对应的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="mymybatis.UserMapper">
<select id="findById" parameterType="java.lang.Integer" resultType="com.cqupt.entity.User">
select * from user where id = ?
</select>
<select id="findAll" resultType="com.cqupt.entity.User">
select * from user
</select>
</mapper>
mybatis配置文件中配置数据源,与注册接口对应的Mapper.xml文件,Mapper.xml文件中的命名空间需要设置为其对应接口的全类名,使得生成代理对象的时候,能过通过接口的全类名找到对应的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>
<!-- 数据源 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://locahost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 注册UserMapper.xml -->
<mappers>
<mapper resource="com/southwind/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
3. 根据 XML,通过 JDK 动态代理(反射)完成自定义接口的具体实现
JDK提供的实现动态代理相关的是一个接口InvocationHandler与动态代理类Proxy,实现的主要分为以下几个步骤
1、继承并实现一个InvocationHandler的实现类,该实现类的作用就是实现需要增强的业务逻辑
2、创建一个该接口的动态代理类,传入InvocationHandler实现类对象。
3、使用生成的动态代理实例调用接口的方法实现
package mymybatis;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
//实现InvocationHandler的对象
public class MyInvocationHandler implements InvocationHandler {
//为目标对象创建动态代理对象
public Object getInstance(Class cls){
Object newProxyInstance = Proxy.newProxyInstance(
cls.getClassLoader(),//第一个参数为类参加载器
new Class[]{cls},//第二个参数为需要代理的接口
this//第三个参数为InvocationHandler实例,调用代理对象的时候,会调用传递的InvocationHandler实例中的invoke方法。
);
return newProxyInstance;//返回生成的代理对象
}
@Override //调用接口执行的部分参数分别为 代理对象,执行的方法,参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1、读取xml文件中数据源的信息
Map<String, String> dataSourceProperty = getDataSourceProperty();
//2、创建数据库连接对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(dataSourceProperty.get("driver"));
dataSource.setJdbcUrl(dataSourceProperty.get("url"));
dataSource.setUser(dataSourceProperty.get("username"));
dataSource.setPassword(dataSourceProperty.get("password"));
Connection connection = dataSource.getConnection();
//3、执行 SQL,获取结果集
//获取方法对应的statement 解析获取到需要执行SQL语句的相关信息
Map<String, String> sqlMap = parseSatement(method.getName());
String sql = sqlMap.get("sql");
String resultType = sqlMap.get("resultType");
String parameterType = sqlMap.get("parameterType");
//4、根据实体类解析结果集 结果集-Java对象
return createObj(connection,sql, resultType,parameterType,args);
}
4. 自动解析结果集,映射成 XML 中配置的 JavaBean
读取xml文件中数据源的信息,结果以Map的形式返回
/**
* 解析XML获取数据源参数
* @return
*/
public Map<String,String> getDataSourceProperty(){
Map<String,String> maps = new HashMap<>();
//通过dom4j读取config.xml
try {
SAXReader reader = new SAXReader();
//这里为数据源文件的地址
Document document = reader.read("src/main/resources/config.xml");
//获取配置文件中的数据
Element rootElement = document.getRootElement();
Iterator<Element> iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
Element next = iterator.next();
if("environments".equals(next.getName())){
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");
maps.put(name, value);
}
}
}
}
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
return maps;
}
根据接口调用的方法获取对应的statement,并解析获取到需要执行SQL语句的相关信息,以Map形式返回结果,主要解析sql语句,返回值类型,参数类型
/**
* 解析XML获取对应的SQL
* @return
*/
public Map<String,String> parseSatement(String methodName){
Map<String,String> maps = new HashMap<>();
//通过dom4j读取config.xml
try {
SAXReader reader = new SAXReader();
Document document = reader.read("src/main/java/mymybatis/UserMapper.xml");
//获取配置文件中的数据
Element rootElement = document.getRootElement();
Iterator<Element> iterator = rootElement.elementIterator();
while (iterator.hasNext()) {
Element next = iterator.next();
String id = next.attributeValue("id");
if(id.equals(methodName)){
String sql = next.getText();
maps.put("sql", sql);
String parameterType = next.attributeValue("parameterType");
String resultType = next.attributeValue("resultType");
maps.put("resultType",resultType);
maps.put("parameterType",parameterType);
}
}
} catch (DocumentException e) {
e.printStackTrace();
}
return maps;
}
根据实体类解析结果集 结果集-Java对象
/**
* 执行SQL,并映射结果集
* @return
*/
public Object createObj(Connection connection,String sql,String resultType,String parameterType,Object[] args){
Object result = null;
try {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//实现传入参数的替换SQL中的占位符
if(parameterType!=null){
switch (parameterType){
case "java.lang.Integer":
preparedStatement.setInt(1,(Integer) args[0]);
break;
case "java.lang.String":
// preparedStatement.setString();
break;
}
}
ResultSet resultSet = preparedStatement.executeQuery();
//将结果集映射成 Java 对象
try {
Class aClass = Class.forName(resultType);
result = parseObject(resultSet, aClass);
} catch (Exception e) {
e.printStackTrace();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return result;
}
//根据查询到的ResultSet完成Javabean的映射
public Object parseObject(ResultSet resultSet,Class aClass) throws SQLException {
//多行数据,映射成集合
List list = new ArrayList();
ResultSetMetaData metaData = resultSet.getMetaData();
try {
while (resultSet.next()) {
String columnName = null;
String columnTypeName = null;
Object object = aClass.getConstructor(null).newInstance(null);
Object value = null;
for (int i = 1; i <= metaData.getColumnCount(); i++) {
columnName = metaData.getColumnName(i);
columnTypeName = metaData.getColumnTypeName(i);
switch (columnTypeName){
case "INT":
value = resultSet.getInt(columnName);
break;
case "VARCHAR":
value = resultSet.getString(columnName);
break;
}
//给object赋值
//获取方法
String methodName = "set"+columnName.substring(0,1).toUpperCase()+columnName.substring(1);
//获取实体类属性
Field declaredField = aClass.getDeclaredField(columnName);
Method method = aClass.getMethod(methodName, declaredField.getType());
method.invoke(object,value);
}
list.add(object);
}
} catch (Exception e) {
e.printStackTrace();
}
if(list.size()==0) return null;
if(list.size() == 1) return list.get(0);
return list;
}
5.动态代理的测试
package mymybatis;
import com.cqupt.entity.User;
import java.util.List;
public class Test {
public static void main(String[] args) {
//创建handler对象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
//传入需要代理的接口,生成其动态代理对象
UserMapper userMapper = (UserMapper) myInvocationHandler.getInstance(UserMapper.class);
//调用代理对象的方法
List<User> all = userMapper.findAll();
for (User user : all) {
System.out.println(user);
}
System.out.println("******************");
User byId = userMapper.findById(1);
System.out.println(byId);
}
}
执行后的查询结果:通过接口代理的方式可以成功的从数据库中查询到结果,并完成对象的封装