参考博客
https://blog.csdn.net/Kurozaki_Kun/article/details/81482212
个人理解
1 读取Mybatis配置文件 2 数据库连接信息 3 读取Mapper.xml文件 4 1.把增删改查节点信息封装成一个MapperInfo 5 2.创建一个Map存放(接口全路径名+方法名, MapperInfo); 6 3.提供方法,通过接口获取MapperInfo信息 7 创建一个动态搭理接口SqlSession 8 通过传入核心Sql执行类SqlExecuteHandler获得被代理对象。 9 创建SqlExecuteHandler执行类 10 1.通过接口方法,获取接口名称和接口方法名称,然后获得MapperInfo信息 11 2.获得了MapperInfo信息后,获取执行的sql语句,把参数赋值进去,然后执行得到结果。 12 主要核心点 13 接口和Mapper之间的关系,用Map(接口+方法名, MapperInfo)进行关联 14 sql的执行,通过接口生成的代理对象调用方法的时候,执行InvokerHandler方法,通过代理的方法名获取MapperInfo信息,然后获取其中的sql进行执行操作。 15 查询结果返回的是ResultSet, 需要映射成实体类。
实现步骤
1.创建一个maven项目,导入dom4j和mysql包。
<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>com.jtfr</groupId> <artifactId>SimpleMybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SimpleMybatis</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies> </project>
2.创建测试表
1 CREATE DATABASE db_test; 2 3 USE db_test; 4 5 CREATE TABLE `tb_user` ( 6 `id` INT(11) NOT NULL, 7 `name` VARCHAR(20) NOT NULL, 8 `age` TINYINT(4) DEFAULT '0', 9 `addr` VARCHAR(20) NOT NULL, 10 PRIMARY KEY (`id`) 11 ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 12 13 INSERT INTO tb_user(id,NAME,age,addr) VALUES(1,'ckm1', 100, 'sz1'); 14 INSERT INTO tb_user(id,NAME,age,addr) VALUES(2,'ckm2', 100, 'sz2'); 15 INSERT INTO tb_user(id,NAME,age,addr) VALUES(3,'ckm3', 100, 'sz3'); 16 INSERT INTO tb_user(id,NAME,age,addr) VALUES(4,'ckm4', 100, 'sz4'); 17 INSERT INTO tb_user(id,NAME,age,addr) VALUES(5,'ckm5', 100, 'sz5'); 18 COMMIT;
3.创建测试用的实体类
1 package com.jtfr.entity; 2 3 /** 4 * 用户实体 5 * @author ckm 6 * 7 */ 8 public class User { 9 10 private int id; 11 private String name; 12 private int age; 13 private String addr; 14 15 public int getId() { 16 return id; 17 } 18 19 public void setId(int id) { 20 this.id = id; 21 } 22 23 public String getName() { 24 return name; 25 } 26 27 public void setName(String name) { 28 this.name = name; 29 } 30 31 public int getAge() { 32 return age; 33 } 34 35 public void setAge(int age) { 36 this.age = age; 37 } 38 39 public String getAddr() { 40 return addr; 41 } 42 43 public void setAddr(String addr) { 44 this.addr = addr; 45 } 46 47 48 @Override 49 public String toString() { 50 return "User{" + 51 "id=" + id + 52 ", name='" + name + '\'' + 53 ", age=" + age + 54 ", addr='" + addr + '\'' + 55 '}'; 56 } 57 }
4.创建操作的接口
1 package com.jtfr.dao; 2 3 import com.jtfr.entity.User; 4 5 public interface UserDao { 6 7 User getUserInfo(int id); 8 9 int updateUserName(String newName, int id); 10 11 int insertUser(int id, String name, int age, String addr); 12 }
5.模仿Mybaitis编写对应接口的Mapper.xml
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <mapper namespace="com.jtfr.dao.UserDao"> 3 <select id="getUserInfo" resultType="com.jtfr.entity.User"> 4 select * from tb_user 5 where id = ?; 6 </select> 7 8 <update id="updateUserName"> 9 update tb_user set name = ? 10 where id = ? 11 </update> 12 13 <insert id="insertUser"> 14 insert into tb_user 15 values(?, ?, ?, ?); 16 </insert> 17 </mapper>
6.读取和记录Mapper.xml文件
6.1需要有一个MapperInfo来存放Mapper.xml里面的每一个操作信息。
1 package com.jtfr.core; 2 3 public class MapperInfo { 4 5 private QueryType queryType; 6 private String interfaceName; 7 private String methodName; 8 private String sql; 9 private String resultType; 10 11 public QueryType getQueryType() { 12 return queryType; 13 } 14 15 public void setQueryType(QueryType queryType) { 16 this.queryType = queryType; 17 } 18 19 public String getSql() { 20 return sql; 21 } 22 23 public void setSql(String sql) { 24 this.sql = sql; 25 } 26 27 public String getResultType() { 28 return resultType; 29 } 30 31 public void setResultType(String resultType) { 32 this.resultType = resultType; 33 } 34 35 public String getInterfaceName() { 36 return interfaceName; 37 } 38 39 public void setInterfaceName(String interfaceName) { 40 this.interfaceName = interfaceName; 41 } 42 43 public String getMethodName() { 44 return methodName; 45 } 46 47 public void setMethodName(String methodName) { 48 this.methodName = methodName; 49 } 50 51 @Override 52 public String toString() { 53 return "MapperInfo{" + 54 "queryType=" + queryType + 55 ", interfaceName='" + interfaceName + '\'' + 56 ", methodName='" + methodName + '\'' + 57 ", sql='" + sql + '\'' + 58 ", resultType='" + resultType + '\'' + 59 '}'; 60 } 61 }
6.2需要一个枚举类,记录Mybatis的操作。
1 package com.jtfr.core; 2 3 public enum QueryType { 4 SELECT, UPDATE, INSERT, DELETE; 5 6 /** 7 * 这里的作用,判断标签是否是四种其中的一种 8 * @param v 9 * @return 10 */ 11 public static QueryType value(String v) { 12 return valueOf(v.toUpperCase()); 13 } 14 }
6.3读取Mapper.xml文件,并且处理相关数据,最后形成一个Map<String, MapperInfo>存放。
1 package com.jtfr.core; 2 3 import java.io.File; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 import org.dom4j.Document; 8 import org.dom4j.DocumentException; 9 import org.dom4j.Element; 10 import org.dom4j.io.SAXReader; 11 12 public enum SqlMappersHolder { 13 14 INSTANCE; 15 16 private Map<String, MapperInfo> mi = null; 17 18 SqlMappersHolder() { 19 if (mi != null) 20 return; 21 mi = new HashMap<String, MapperInfo>(); 22 // 获取mapper.xml文件所在路径 23 File dir = new File(SqlMappersHolder.class.getClassLoader().getResource(Config.DEFAULT.getMapperPath()).getFile()); 24 25 // 用dom4j解析 26 SAXReader reader = new SAXReader(); 27 try { 28 for (String file : dir.list()) { 29 // 读取mapper.xml 30 Document doc = reader.read(new File(dir, file)); 31 Element root = doc.getRootElement(); 32 // 获取接口全路径名 33 String className = root.attributeValue("namespace"); 34 35 for (Object o : root.elements()) { 36 Element e = (Element) o; 37 38 MapperInfo info = new MapperInfo(); 39 info.setQueryType(QueryType.value(e.getName())); 40 info.setInterfaceName(className); 41 info.setMethodName(e.attributeValue("id")); 42 info.setResultType(e.attributeValue("resultType")); 43 info.setSql(e.getText()); 44 45 mi.put(idOf(className, e.attributeValue("id")), info); 46 } 47 } 48 } catch (DocumentException e) { 49 e.printStackTrace(); 50 } 51 } 52 53 public MapperInfo getMapperInfo(String className, String methodName) { 54 return mi.get(idOf(className, methodName)); 55 } 56 57 /* 58 * 接口全路径+"."+方法名作为唯一id 59 */ 60 private String idOf(String className, String methodName) { 61 return className + "." + methodName; 62 } 63 }
7.通过接口生成动态代理对象
7.1JDK动态代理需要先有一个实现InvocationHandler的处理类
1 package com.jtfr.core; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.InvocationHandler; 5 import java.lang.reflect.InvocationTargetException; 6 import java.lang.reflect.Method; 7 import java.sql.PreparedStatement; 8 import java.sql.ResultSet; 9 import java.sql.SQLException; 10 11 public class SqlExecuteHandler implements InvocationHandler { 12 13 public Object invoke(Object proxy, Method method, Object[] params) throws Throwable { 14 // 传入的要被执行的接口方法,通过获取接口名和方法名,然后获取要MapperInfo信息。 15 MapperInfo info = getMapperInfo(method); 16 17 // 通过MapperInfo和传入的参数,执行Sql语句,并且返回结果。 18 return executeSql(info, params); 19 } 20 21 private MapperInfo getMapperInfo(Method method) throws Exception { 22 MapperInfo mapperInfo = SqlMappersHolder.INSTANCE.getMapperInfo(method.getDeclaringClass().getName(), method.getName()); 23 if (mapperInfo == null) { 24 throw new Exception("Mapper not found for method: " + 25 method.getDeclaringClass().getName() + "." + method.getName()); 26 } 27 return mapperInfo; 28 } 29 30 private Object executeSql(MapperInfo info, Object[] params) 31 throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { 32 Object result = null; 33 PreparedStatement pstat = ConnectionManager.get().prepareStatement(info.getSql()); 34 for (int i = 0; i < params.length; i++) { 35 pstat.setObject(i + 1, params[i]); 36 } 37 38 39 if (info.getQueryType() == QueryType.SELECT) { 40 ResultSet rs = pstat.executeQuery(); 41 rs.first(); 42 // 将查询结果映射为ava类或基本数据类型 43 // 目前简化版仅支持String和int两种类型 44 if (rs.getMetaData().getColumnCount() == 1) { 45 switch (info.getResultType()) { 46 case "int": 47 result = rs.getInt(1); 48 break; 49 default: 50 result = rs.getString(1); 51 } 52 } else { 53 Class<?> resultTypeClass = Class.forName(info.getResultType()); 54 Object inst = resultTypeClass.newInstance(); 55 for (Field field : resultTypeClass.getDeclaredFields()) { 56 String setterName = "set" + 57 field.getName().substring(0, 1).toUpperCase() + 58 field.getName().substring(1); 59 Method md; 60 61 switch (field.getType().getSimpleName()) { 62 case "int": 63 // 获取方法 64 md = resultTypeClass.getMethod(setterName, new Class[]{int.class}); 65 // 反射执行方法对 field进行赋值。 66 md.invoke(inst, rs.getInt(field.getName())); 67 break; 68 /* 其他类型还没来得及测试,后面修复 69 case "long": 70 md = resultTypeClass.getMethod(setterName, new Class[]{long.class}); 71 md.invoke(inst, rs.getLong(field.getName())); 72 break;*/ 73 case "string": 74 md = resultTypeClass.getMethod(setterName, new Class[]{String.class}); 75 md.invoke(inst, rs.getString(field.getName())); 76 break; 77 78 79 80 default: 81 md = resultTypeClass.getMethod(setterName, new Class[]{String.class}); 82 md.invoke(inst, rs.getString(field.getName())); 83 } 84 } 85 result = inst; 86 } 87 } else { 88 result = pstat.executeUpdate(); 89 } 90 pstat.close(); 91 return result; 92 } 93 94 }
7.2生成动态代理对象。
1 package com.jtfr.core; 2 3 import java.lang.reflect.Proxy; 4 5 public class SqlSession { 6 7 @SuppressWarnings("unchecked") 8 public <T> T getMapper(Class<T> cls) { 9 return (T) Proxy.newProxyInstance(cls.getClassLoader(), 10 new Class[]{cls}, 11 new SqlExecuteHandler()); 12 } 13 }
剩下的待补充