手写MyBatis底层机制
读取配置文件,得到数据库连接
思路
- 引入必要的依赖
- 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
- 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
实现
- 引入必要的依赖
<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.49</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.4</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency></dependencies>
- 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
<?xml version="1.0" encoding="UTF-8" ?>
<database><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/hsp_mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="zy"/>
</database>
- 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
public class ZyConfiguration {//属性 类加载器private ClassLoader classLoader =ClassLoader.getSystemClassLoader();//读取xml文件信息并处理public Connection build(String resource) {Connection connection = null;//加载配置文件,获取对应的InputStream流InputStream resourceAsStream =classLoader.getResourceAsStream(resource);//解析xml文件SAXReader reader = new SAXReader();try {Document document = reader.read(resourceAsStream);Element root = document.getRootElement();//解析rootElementSystem.out.println("root= "+root);return evalDataSource(root);} catch (DocumentException e) {throw new RuntimeException(e);}}//解析xml文件 并返回一个连接private Connection evalDataSource(Element node) {Iterator property = node.elementIterator("property");String driverClassName = null;String url = null;String username = null;String password = null;//遍历node子节点 获取属性值while(property.hasNext()){Element pro = (Element)property.next();String name = pro.attributeValue("name");String value = pro.attributeValue("value");//判断是否得到了name 和 valueif (name == null || value == null){throw new RuntimeException("property 节点没有设置name 或 value属性");}switch (name){case "driverClassName":driverClassName = value;break;case "url":url = value;break;case "username":username = value;break;case "password":password = value;break;default:throw new RuntimeException("属性名没有匹配到");}}Connection connection = null;try {Class.forName(driverClassName);connection = DriverManager.getConnection(url, username, password);} catch (Exception e) {throw new RuntimeException(e);}return connection;}}
编写执行器,输入SQL语句,完成操作
思路
- 需要写一个实体类,对应monster表
- 编写接口executor
- 实现接口,编写自己的执行器
- 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作
实现
- 需要写一个实体类,对应monster表
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {private Integer id;private Integer age;private String name;private String email;private Date birthday;private double salary;private Integer gender;
}
- 编写接口executor
public interface Executor {public <T> T query(String statement,Object parameter);
}
- 实现接口,编写自己的执行器
public class ZyExecutor implements Executor{private ZyConfiguration zyConfiguration = new ZyConfiguration();@Overridepublic <T> T query(String sql, Object parameter) {Connection connection = getConnection();//查询返回的结果集ResultSet set = null;PreparedStatement pre = null;try {pre = connection.prepareStatement(sql);//设置参数,如果参数多,用数组处理pre.setString(1, parameter.toString());set = pre.executeQuery();//把set数据封装到对象 -- monsterMonster monster = new Monster();//简化处理 认为返回的结果就是一个monster记录//遍历结果集while(set.next()){monster.setId(set.getInt("id"));monster.setName(set.getString("name"));monster.setEmail(set.getString("email"));monster.setAge(set.getInt("age"));monster.setGender(set.getInt("gender"));monster.setBirthday(set.getDate("birthday"));monster.setSalary(set.getDouble("salary"));}return (T)monster;} catch (SQLException e) {throw new RuntimeException(e);}finally {try {if (set != null) {set.close();}if (pre != null) {pre.close();}if (connection != null) {connection.close();}} catch (Exception e) {throw new RuntimeException(e);}}}public Connection getConnection(){//Configuration类 返回连接,通过连接对数据库进行操作return zyConfiguration.build("zy_mybatis.xml");}
}
- 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作
将Sqlsession封装到执行器
思路
- 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
- 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
实现
- 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
public class ZySqlSession {//搭建连接和执行器之间的桥梁//执行器private Executor executor = new ZyExecutor();//配置private ZyConfiguration zyConfiguration = new ZyConfiguration();//操作DB 的具体方法//SelectOne 返回一条记录-对象public <T> T selectOne(String statement,Object parameter){return executor.query(statement,parameter);}
}
- 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
//操作DB 的具体方法//SelectOne 返回一条记录-对象public <T> T selectOne(String statement,Object parameter){return executor.query(statement,parameter);}
开发Mapper接口和Mapper.xml
思路
- 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
- 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
- monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
实现
- 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
public interface MonsterMapper {public Monster getMonsterById(Integer id);
}
- 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
- monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper"><!-- 实现配置接口方法 getMonsterById--><select id="getMonsterById" resultType="com.code_study.entity.Monster">SELECT * FROM monster WHERE id = ?</select>
</mapper>
开发MapperBean,可以和Mapper接口相映射
思路
- 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
- 开发 MapperBean类,记录接口信息和接口下的所有方法
- Function类 对应 monsterMapper.xml中的信息
- MapperBean类 对应 MonsterMapper 接口中的信息
实现
- 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
//对应 monsterMapper.xml中的信息
public class Function {private String sqlType;//sql类型,比如select,insert,update,deleteprivate String funcName;//方法名private String sql;//执行的sql语句private Object resultType;//返回类型private String parameterType;//入参类型
}
- 开发 MapperBean类,记录接口信息和接口下的所有方法
//对应 MonsterMapper 接口中的信息
public class MapperBean {private String interfaceName;//接口名// 接口下的所有方法private List<Function> functions;
}
- Function类 对应 monsterMapper.xml中的信息
- MapperBean类 对应 MonsterMapper 接口中的信息
在Configuration中解析MapperXML获取MapperBean对象
思路
- 在Configuration 添加方法readMapper(String path)
- 通过 path 读取接口对应的Mapper方法
- 保存接口下所有的方法信息
- 封装成 MapperBean对象
实现
- 在Configuration 添加方法readMapper(String path)
- 通过 path 读取接口对应的Mapper方法
- 保存接口下所有的方法信息
- 封装成 MapperBean对象
//解析MapperXML获取MapperBean对象//path = xml的路径+文件名 是从类的加载路径计算的(如果放在resource目录下 之间传xml文件名即可)public MapperBean readMapper(String path) {MapperBean mapperBean = new MapperBean();InputStream resourceAsStream = classLoader.getResourceAsStream(path);SAXReader reader = new SAXReader();try {Document document = reader.read(resourceAsStream);Element root = document.getRootElement();String namespace = root.attributeValue("namespace");mapperBean.setInterfaceName(namespace);List<Function> list = new ArrayList<>();//保存接口下所有的方法信息//得到root的迭代器Iterator iterator = root.elementIterator();while(iterator.hasNext()){Element e = (Element)iterator.next();String sqlType = e.getName().trim();String sql = e.getText().trim();String funcName = e.attributeValue("id");String resultType = e.attributeValue("resultType");//ResultType 返回的是一个Object对象 ->反射Object instance = Class.forName(resultType).newInstance();//封装 function 对象Function function = new Function();function.setSql(sql);function.setSqlType(sqlType);function.setFuncName(funcName);function.setResultType(instance);//将封装好的function对象 放入 list中list.add(function);mapperBean.setFunctions(list);}} catch (Exception e) {throw new RuntimeException(e);}return mapperBean;}
动态代理Mapper方法
思路
- 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
- 编写动态代理类 实现 InvocationHandler 接口
- 取出mapperBean的functions 遍历
- 判断 当前要执行的方法和function.getFunctionName是否一致
- 调用方法返回 动态代理对象
- 编写SqlSessionFactory 会话工厂,可以返回SqlSession
实现
-
编写动态代理类 实现 InvocationHandler 接口
-
在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
//返回mapper的动态代理对象public <T> T getMapper(Class<T> clazz){return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},new ZyMapperProxy(zyConfiguration,clazz,this));}
- 取出mapperBean的functions 遍历
- 判断 当前要执行的方法和function.getFunctionName是否一致
- 调用方法返回 动态代理对象
public class ZyMapperProxy implements InvocationHandler {private ZySqlSession zySqlSession;private String mapperFile;private ZyConfiguration zyConfiguration;public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {this.zySqlSession = zySqlSession;this.zyConfiguration = zyConfiguration;this.mapperFile = clazz.getSimpleName() + ".xml";}//当执行Mapper接口的代理对象方法时,会执行到invoke方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);//判断是否为当前xml文件对应的接口if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){return null;}//取出mapperBean的functionsList<Function> functions = mapperBean.getFunctions();//判断当前的mapperBean 解析对应的MapperXML后,有方法if (null != functions || 0 != functions.size()){for (Function function : functions) {//当前要执行的方法和function.getFunctionNameif (method.getName().equals(function.getFuncName())){if ("SELECT".equalsIgnoreCase(function.getSqlType())){return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));}}}}return null;}
}
- 编写SqlSessionFactory 会话工厂,可以返回SqlSession
public class ZySqlSessionFactory {public static ZySqlSession open(){return new ZySqlSession();}
}
测试
@Test
public void openSession(){ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();System.out.println("zySqlSession= "+zySqlSession);MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);Monster monster = mapper.getMonsterById(1);System.out.println("monster= "+monster);
}