一、dom4j解析XML文件
- 为了方便我们实践各种操作,我们创建了一个新的模块:parse-xml-by-dom4j
- 为新模块设置打包方式和配置各种依赖
- 将之前模块中的 mybatis-congfig.xml 和 CarMapper.xml 文件直接拷贝到了 resource 文件夹下
🌔 1、解析核心配置文件 与 映射文件 【完整代码】
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/**
* @author Bonbons
* @version 1.0
*/
public class ParseXMLByDom4jTest {
//解析SQL映射文件
@Test
public void testParseSqlMapperXML() throws Exception{
//利用Reader获取数据流,也就是找到我们要解析的XML文件
SAXReader reader = new SAXReader();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
Document document = reader.read(is);
//获取namespace
String xpath = "/mapper";
Element mapper = (Element) document.selectSingleNode(xpath);
String namespace = mapper.attributeValue("namespace");
System.out.println(namespace);
//获取当前节点下的所有子节点
List<Element> mapperElements = mapper.elements();
//遍历
mapperElements.forEach(element -> {
String id = element.attributeValue("id");
System.out.println(id);
//如果获取标签没有的属性,并不会报错而是返回mull
String resultType = element.attributeValue("resultType");
System.out.println(resultType);
//获取标签中的sql语句[Trim的作用是去除前后空白,也有个getText方法]
String sql = element.getTextTrim();
System.out.println(sql);
//利用replaceAll方法结合正则表达式,将占位符替换为 ?
String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
System.out.println(newSql);
});
}
@Test
public void testParseMyBatisConfigXML() throws DocumentException {
//通过SAXReader对象解析xml
SAXReader reader = new SAXReader();
//获取输入流
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
//读取xml文件,返回整个xml文件的文档对象
Document document = reader.read(is);
//我们利用xpath定位xml中的元素
String xpath = "/configuration/environments";
//文档类提供一个selectSingleNode方法去查找元素
Element environments = (Element) document.selectSingleNode(xpath);
//获取属性的值
String defaultId = environments.attributeValue("default");
System.out.println("环境id:" + defaultId);
//获取具体的环境,我们仍然需要xpath去定位[如果此处不添加单引号就获取不到具体的属性值]
//如果想获取环境的属性,需要采用 environment[@属性名]的方式
xpath += "/environment[@id='"+defaultId+"']";
Element environment = (Element) document.selectSingleNode(xpath);
// System.out.println(environment);
//利用element方法获取指定元素下的子节点
Element transcationManager = environment.element("transactionManager");
//查看事务管理器的类型
String transactionType = transcationManager.attributeValue("type");
System.out.println("事务管理器:" + transactionType);
//获取dataSource结点
Element dataSource = environment.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println("数据源:" + dataSourceType);
//获取dataSource下的所有子节点
List<Element> propertyElements = dataSource.elements();
//遍历
propertyElements.forEach(e -> {
String name = e.attributeValue("name");
String value = e.attributeValue("value");
System.out.println(name + ":" + value);
});
//解析mapper
//利用xpath获取路径
xpath = "//mapper";
List<Node> mappers = document.selectNodes(xpath);
//遍历mappers
mappers.forEach(e -> {
//将原型强转为Element类型
Element mapperElement = (Element) e;
//获取resource属性
String resource = mapperElement.attributeValue("resource");
System.out.println(resource);
});
}
}
🌔 2、解析核心配置文件的详细步骤
(1)展示一下我的 mybatis-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>
<environments default="powernodeDB">
<environment id="powernodeDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</dataSource>
</environment>
<!--添加一个新的环境-->
<environment id="mybatisDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.myql.cj.hdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/shop"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--sql映射文件创建好之后,需要将该文件路径配置到这里-->
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
(2)我们在 test/java 目录下创建一个测试类:ParseXMLByDom4jTest
-
如果我们想解析一个文件,首先就要定位到这个文件的位置:
-
我们通过 SAXReader 的实例去读取文件的数据流
```java SAXReader reader = new SAXReader(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml); Document document = reader(is);
-
最后得到的是整个xml文件的文档实例
-
我们需要定位到具体的表现,所以我们实现要定位到环境标签的位置:
-
我们使用 xpath 记录标签的位置
-
通过 Document t的 selectSingleNode 去查找标签
-
通过标签的 attributeValue(属性) 方法,可以获取标签的属性值
```java String xpath = "/configuration/environments"; Element environments = (Element) document.selectSingleNode(xpath); String default = environments.attributeValue("default"); System.out.println("环境id:" + defaultId);
* 之后与此类似,就是一层一层的解析**xml**文件中的所有标签及其属性 * 接下来我们去查看具体的环境
- 还是需要利用 xpath 去定位标签【因为可能存在多个环境,所以我们可以通过id去指定查看哪个环境】
- 还是需要 selectSingleNode 根据 xpath 获取标签
```java xpath += "/environment[@id='"+defaultId+"']"; Element environment = (Element) document.selectSingleNode(xpath); System.out.println(environment);
* 因为之后与上述相同,且配置信息大部分都属于当前环境的子节点,所以给出说明和代码【注释在代码里面】
- 我们使用 element(“标签名”) 获取子节点
- 获得事务管理器的标签,并查看管理器的类型
```java //获取子节点 -- 事务管理器 Element transcationManager = environment.element("transactionManager"); //查看事务管理器的类型 String transactionType = transcationManager.attributeValue("type"); System.out.println("事务管理器:" + transactionType);
- 获取 dataSource 标签
```java Element dataSource = environment.element("dataSource"); String dataSourceType = dataSource.attributeValue("type"); System.out.println("数据源:" + dataSourceType);
- 上面我们使用 element(标签名) 获取当前结点的一个指定子节点,我们可以使用 elements() 获取当前结点的所有子节点
```java //获取dataSource下的所有子节点 List<Element> propertyElements = dataSource.elements(); //遍历 propertyElements.forEach(e -> { String name = e.attributeValue("name"); String value = e.attributeValue("value"); System.out.println(name + ":" + value); });
* 还有一个 **mapper** 标签,但是它不是环境的子节点,所以我们呢要重新定位,其余步骤与上述内容类似 ```java //利用xpath获取路径 xpath = "//mapper"; List
mappers = document.selectNodes(xpath); //遍历mappers mappers.forEach(e -> { //将原型强转为Element类型 Element mapperElement = (Element) e; //获取resource属性 String resource = mapperElement.attributeValue("resource"); System.out.println(resource); });
- 运行结果如下:【之所以输出多次CarMapper是我测试,在mapper标签里添加了多个重复资源】
🌔 3、解析映射文件xml文件 【CarMapper.xml】
(1)老样子,先看看 CarMapper.xml 文件中都有什么 【一条查询、一条插入语句】
insert into t_cat values (null, #{
carNum}, #{
brand}, #{
guidePrice}, #{
produceTime}, #{
carType})
(2)如果想解析这个xml文件,整体思路梳理:
- 我们要通过数据流与SAXReader实例定位到这个文件,最后获得document文档
- 我们要利用 xpath 和 selectSingleNode 获取根标签,此处为 namespace 标签,我们可以通过 attributeValue 方法获取属性值
- 我们通过 elements() 获取所有子节点,进而查看属性值
- 我们还可以通过 getTextTrim() 查看具体的SQL语句
- 我们还可以将SQL语句的占位符替换成JDBC的格式
- 代码如下:详情见注释
@Test
public void testParseSqlMapperXML() throws Exception{
//利用Reader获取数据流,也就是找到我们要解析的XML文件
SAXReader reader = new SAXReader();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
Document document = reader.read(is);
//获取namespace
String xpath = "/mapper";
Element mapper = (Element) document.selectSingleNode(xpath);
String namespace = mapper.attributeValue("namespace");
System.out.println(namespace);
//获取当前节点下的所有子节点
List mapperElements = mapper.elements();
//遍历
mapperElements.forEach(element -> {
String id = element.attributeValue("id");
System.out.println(id);
//如果获取标签没有的属性,并不会报错而是返回mull
String resultType = element.attributeValue("resultType");
System.out.println(resultType);
//获取标签中的sql语句[Trim的作用是去除前后空白,也有个getText方法]
String sql = element.getTextTrim();
System.out.println(sql);
//利用replaceAll方法结合正则表达式,将占位符替换为 ?
String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
System.out.println(newSql);
});
}
二、手写Godbatis框架
- 并不是和mybatis一样的完整框架,只是实现了一些基本功能的删减版框架
- 接下来我们从整体上分析一下这个框架:
1、 创建一个新的模块–godbatis;
(1)确定打包方式和导入相关依赖【修改pom.xml文件】
jar
org.dom4j
dom4j
2.1.3
jaxen
jaxen
1.2.0
junit
junit
4.13.2
test
(2)为了方便从类路径加载资源,我们写一个工具类 – Resources.java
package org.god.ibatis.utiils;
import java.io.InputStream;
/**
* godbatis 工具类
* 从类路径下加载资源
* @author Bonbons
* @version 1.0
*/
public class Resources {
/**
* 工具类的构造器都是私有的 >> 不需要创建对象,提供的都是静态方法
*/
public Resources(){
}
/**
* 从类路径加载资源
* @param resource 放在类路径当中的资源文件
* @return 指向资源文件的一个输入流
*/
public static InputStream getResourceAsStream(String resource){
return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
}
}
2、 创建SqlSessionFactoryBuilder类,为其设计一个build方法,可以返回一个SqlSessionFactory的对象;
package org.god.core;
import java.io.InputStream;
/**
* SqlSessionFactory对象构建器
* @author 老杜
* @version 1.0
* @since 1.0
*/
public class SqlSessionFactoryBuilder {
/**
* 创建构建器对象
*/
public SqlSessionFactoryBuilder() {
}
/**
* 获取SqlSessionFactory对象
* 该方法主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
* @param inputStream 指向核心配置文件的输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream inputStream){
// 解析配置文件,创建数据源对象
// 解析配置文件,创建事务管理器对象
// 解析配置文件,获取所有的SQL映射对象
// 将以上信息封装到SqlSessionFactory对象中
// 返回
return null;
}
}
3、 既然我们要通过build方法返回SqlSessionFactory的实例,那么我们要知道SqlSessionFactory包含哪些属性呢?;
- 使用了哪种事务管理器:此处我们只实现JDBC的事务管理器
- SQL映射对象集合:为了方便,我们将SQL语句抽象出来,通过一个简单的java类,封装了SQL语句的resultType和语句内容
(1)我们思考一个问题,对于事务管理器和数据源类型都有多种,难道我们他们彼此之间没有联系吗?
实则不然,对于两种事务管理器、三种数据源类型他们各自都要遵守自己的规范,也就是我们要通过实现接口的方式来完成。【面向接口编程】
(2)我们定义一个事务管理器的接口
package org.god.ibatis.core;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 事务管理器接口,所有的事务管理器都应该实现该接口
* 实际上接口也只有两个:JDBC、MANAGED事务管理器
* 就是提供事务管理的方法
* @author Bonbons
* @version 1.0
*/
public interface Transaction {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
//开启数据库连接
void openConnection();
//返回连接对象
Connection getConnection();
}
(2)再定义两个类JdbcTransaction和ManagedTransaction分别代表两种事务管理器的实现类,因为我们以JDBC为例,所以此处只给出如何实现Jdbc的事务管理器。
package org.god.ibatis.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* JDBC 事务管理器
* @author Bonbons
* @version 1.0
*/
public class JdbcTransaction implements Transaction{
/**
* 数据源属性,面向接口编程
*/
private DataSource dataSource;
/**
* 自动提交标志
* true 表示自动提交
* false 表示不采用自动提交
*/
private boolean autoCommit;
private Connection connection;
//因为执行SQL语句的时候也需要连接对象,所以为外部提供获取connection的方法
@Override
public Connection getConnection(){
return connection;
}
/**
* 创建事务管理器对象
* @param dataSource
* @param autoCommit
*/
public JdbcTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
@Override
public void commit() {
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void rollback() {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void close() {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void openConnection(){
try {
if(connection == null){
connection = dataSource.getConnection();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
(2)因为事务管理器的实例化需要获取数据源,所以我们将数据源的定义在事务管理器中完成,对外部提供会话对象的获取方法
- 与事务管理器不同,我们不需要去写一个数据源的接口,因为Java的jdk为我们提供了
- 我们自己写的数据源类只需要实现 javax.sql.dataSource 接口
- 数据源的类型有三种: POOLED、UNPOOLED、JNDI三种类型,我么以UNPOLED为例
我们创建一个 UnPooledDataSource.java 用来实现数据源的获取
package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源实现类:UNPOOLED
* 不使用连接池,每一次都新建Connection对象
* @author Bonbons
* @version 1.0
*/
public class UnPooledDataSource implements javax.sql.DataSource{
// private String driver; 数据驱动创建一次就可以了
private String url;
private String username;
private String password;
/**
* 创建一个数据源的对象
* @param driver
* @param url
* @param username
* @param password
*/
public UnPooledDataSource(String driver, String url, String username, String password) {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
//每次获取连接的时候,都创建一个连接对象
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
}
(3)事务管理器和数据源都有了,接下来我们就要考虑获取具体的SQL语句了
- 我们定义一个 MapperdStatement.java 用来封装SQl标签
package org.god.ibatis.core;
/**
* 普通pojo类,作用就是封装一个SQL标签
* 一个MapperdStatement对象对应一个SQL标签
* @author Bonbons
* @version 1.0
*/
public class MapperdStatement {
/**
* sql 代表SQL语句
* resultType 代表返回的结果类型,只有在select语句中不为空
*/
private String sql;
private String resultType;
//有参和无参构造方法
public MapperdStatement(){
}
public MapperdStatement(String sql, String resultType) {
this.sql = sql;
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
@Override
public String toString() {
return "MapperdStatement{" +
"sql='" + sql + '\'' +
", resultType='" + resultType + '\'' +
'}';
}
}
4、 此时已经完成了基本结构的定义,接下来需要完善SqlSessionFactory类和SqlSessionFactoryBuilder类;
(1)完善SqlSessionFactory类,包含具体的属性、构造方法、Get和Set方法
package org.god.ibatis.core;
import java.util.Map;
/**
* 一个数据库对应一个SqlSessionFactory对象,通过该对象可以开启会话并返回
* 一个SqlSession的对象(一对多)
* @author Bonbons
* @version 1.0
*/
public class SqlSessionFactory {
/**
* 事务管理器
*/
Transaction transaction;
/**
* 存放sql语句的Map集合
* key是sqlId
* value是对应SQL标签信息对象
*/
private Map mapperdStatements;
public SqlSessionFactory(){
}
public SqlSessionFactory(Transaction transaction, Map mapperdStatements) {
this.transaction = transaction;
this.mapperdStatements = mapperdStatements;
}
public Transaction getTransaction() {
return transaction;
}
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
}
public Map getMapperdStatements() {
return mapperdStatements;
}
public void setMapperdStatements(Map mapperdStatements) {
this.mapperdStatements = mapperdStatements;
}
}
(2)完成SqlSessionFactoryBuilder类中的build方法【重点内容】
package org.god.ibatis.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utiils.Resources;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Bonbons
* @version 1.0
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder() {
}
/**
* 解析godbatis-config.xml文件,来构造SqlSessionFactory独享
*
* @param in 指向godbatis-config.xml文件的一个输入流
* @return SqlSessionFactory对象
*/
//利用build方法创建SqlSessionFactory
public SqlSessionFactory build(InputStream in) {
SqlSessionFactory factory = null;
try {
//解析godbatis-config.xml文件
//(1)获取文件 >> 转换为document文档
SAXReader reader = new SAXReader();
Document document = reader.read(in);
//(2)获得环境集合节点
Element environments = (Element) document.selectSingleNode("/configuration/environments");
//(3)获得它的属性 >> 默认使用的环境id
String defaultId = environments.attributeValue("default");
//(4)获取具体的环境
Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
//(5)获取子节点 事务管理器和数据源
Element transactionElt = (Element) environment.element("transactionManager");
Element dataSourceElt = (Element) environment.element("dataSource");
//(6)创建JDBC事务管理器的时候需要传入数据源
DataSource dataSource = getDataSource(dataSourceElt);
//获取事务管理器
Transaction transaction = getTransaction(transactionElt, dataSource);
//创建一个集合,保存所有的Mapper.xml的路径
List nodes = document.selectNodes("//mapper"); // "//"代表找到所有的mapper标签,而不是定位到mappers的位置
List sqlMapperXMLPathList = new ArrayList<>();
//遍历nodes,获取其resource属性
nodes.forEach(node -> {
Element mapper = (Element) node;
String resource = mapper.attributeValue("resource");
sqlMapperXMLPathList.add(resource);
});
//获取mapperedStatement [封装的SQl语句]
Map mappedStatements = getMappedStatements(sqlMapperXMLPathList);
//解析完成我们返回SqlSessionFactory
factory = new SqlSessionFactory(transaction, mappedStatements);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
private Map getMappedStatements(List sqlMapperXMLPathList) {
//结果封装 key 为SQL的id,value 为resultType和SQL语句
Map mapperdStatements = new HashMap<>();
//根据我们获得的mapper标签,获得每个Mapper文件的内容
sqlMapperXMLPathList.forEach(sqlMapperXMLPath ->{
try{
//解析每个xml文件
SAXReader reader = new SAXReader();
Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));
Element mapper = (Element) document.selectSingleNode("mapper");
String namespace = mapper.attributeValue("namespace");
//获得所有SQL语句
List elements = mapper.elements();
elements.forEach(element -> {
String id = element.attributeValue("id");
//拼接namespace+id
String sqlId = namespace + "." + id;
String resultType = element.attributeValue("resultType");
String sql = element.getTextTrim();
MapperdStatement mapperdStatement = new MapperdStatement(sql, resultType);
mapperdStatements.put(sqlId, mapperdStatement);
});
}catch (Exception e){
e.printStackTrace();
}
});
return mapperdStatements;
}
private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
//事务管理器的类型
String type = transactionElt.attributeValue("type").trim().toUpperCase();
//创建事务管理器的对象
Transaction transaction = null;
//选择使用哪种事务管理器
if(Const.JDBC_TRANSACTION.equals(type)){
transaction = new JdbcTransaction(dataSource, false);
}else{
transaction = new ManagedTransaction();
}
return transaction;
}
/**
* 获取数据源对象
*
* @param dataSourceElt 数据源跟标签
* @return
*/
private DataSource getDataSource(Element dataSourceElt) {
//利用集合存储DataSource子节点的name和value
Map map = new HashMap<>();
//获取DataSource节点的所有子节点
List propertyElts = dataSourceElt.elements();
//遍历所有子节点,记录元素值
propertyElts.forEach(e -> {
String name = e.attributeValue("name");
String value = e.attributeValue("value");
map.put(name, value);
});
//定义数据源的对象
DataSource dataSource = null;
//获取数据源类型[使用哪种连接池]
String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
//根据不同的类型选择不同的操作[需要传入参数]
if (Const.UN_POOLED_DATASOURCE.equals(type)) {
dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
}
if (Const.POOLED_DATASOURCE.equals(type)) {
dataSource = new PooledDataSource();
}
if (Const.JNDI_DATASOURCE.equals(type)) {
dataSource = new JNDIDataSource();
}
return dataSource;
}
}
(3)为SqlSessionFactory类添加openSession方法,用于开启会话
public SqlSession openSession(){
//利用事务管理器的openSession方法来开启连接,这样确保会话不为空
transaction.openConnection();
//创建SqlSession的对象 [此处进行了修改2]
SqlSession sqlSession = new SqlSession(this);
return sqlSession;
}
4、 为了实现具体的功能,我们提供插入数据和根据id查询数据的功能【为了方便,我们将所有字段都定义为字符串类型】;
- 在SqlSession类中提供 insert 和 selectOne 方法
(1)insert 方法的代码如下:【大部分都标注了注释】
public int insert(String sqlId, Object pojo){
//操作影响数据库表记录的条数
int count = 0;
try {
//获取连接对象
Connection connection = factory.getTransaction().getConnection();
//获取我们封装的SQL语句,此时是使用#{}作为占位符的
String godbatisSql = factory.getMapperdStatements().get(sqlId).getSql();
//将sql语句改为JDBC的格式
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(sql);
//重点在于如何动态的给占位符传值
//为了简化操作,该框架应用的数据库表的字段都是varchar类型
int fromIndex = 0; //记录查询的起始位置
int index = 1; //对应第几个 ?
while(true){
//每个占位符#的位置
int jingIndex = godbatisSql.indexOf("#", fromIndex);
//已经没有#号了
if(jingIndex < 0){
break;
}
//每个占位符右括号的位置
int youKuoHaoIndex = godbatisSql.indexOf("}", fromIndex);
//对应字段名
String propertyName = godbatisSql.substring(jingIndex + 2, youKuoHaoIndex).trim();
//更新下次查找起始位置
fromIndex = youKuoHaoIndex + 1;
//如何获取属性值?[分为三部分:get + 字段首字母大写 + 字段剩余部分]
String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
//
Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);
Object propertyValue = getMethod.invoke(pojo);
ps.setString(index, propertyValue.toString());
//更新索引
index++;
}
//执行SQL语句
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return count;
}
(2)selectOne 的代码块如下:
public Object selectOne(String sqlId, Object param){
Object obj = null;
try {
//获取连接对象
Connection connection = factory.getTransaction().getConnection();
//获取我们封装的SQL集合,key--sqlId、value--resultType和SQL语句
MapperdStatement mapperdStatement = factory.getMapperdStatements().get(sqlId);
//获取sql语句和结果集的类型
String godbatisSql = mapperdStatement.getSql();
String resultType = mapperdStatement.getResultType();
//占位符替换
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-_$]*}", "?");
//参数传递
PreparedStatement ps = connection.prepareStatement(sql);;
//此时占位符只有一个
ps.setString(1,param.toString());
ResultSet rs = ps.executeQuery();
//从结果集中取数据封装成Java对象
if(rs.next()){
// 获取resultType的Class
Class> resultTypeClass = Class.forName(resultType);
//调用无参构造方法创建对象
obj = resultTypeClass.newInstance();
//给User类的属性赋值
ResultSetMetaData rsmd = rs.getMetaData();
//列数
int columnCount = rsmd.getColumnCount();
//依次赋值
for (int i = 0; i < columnCount; i++) {
//获取第i个列名
String propertyName = rsmd.getColumnName(i + 1);
//拼接Set方法名
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
//获取set方法
Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
//调用set方法给obj传值
setMethod.invoke(obj, rs.getString(propertyName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
(3)所有的代码都经过了测试,文末会提供框架的 jar 包
三、项目打包和测试
1、将手写的这个框架打包
- 因为我用的是idea,所有可以一键打包
- 右侧点击Maven >> 找到项目 >> Lifecycle >> install
- 打包后会存储到我们的本地仓库中
2、框架测试
(1)创建了一个新的模块 godbatis-test 用于功能测试
(2)修改 pom.xml 文件,确定打包方式和导入我们需要的依赖
4.0.0
com.powernode
godbatis-test
1.0-SNAPSHOT
jar
org.god.ibatis
godbatis
1.0-SNAPSHOT
junit
junit
4.13.2
test
17
17
UTF-8
没有导入mysql驱动等,因为在原框架中有依赖关系自动就下载了,所以我们此处只额外添加了 Junit 的依赖。
- 那么我们在框架中也导入了 junit 依赖,为什么此处还需要重新导入呢?
因为 junit 的 scope 为 test,在打包的时候就不会将其打包进来【test 代表作用范围为测试】
(3)添加我们的核心配置文件和映射文件
- godbatis-config.xml
- UserMapper.xml
insert into t_user values (#{
id}, #{
name}, #{
age})
(4)编写我们的pojo类,利用普通Java类作为数据库表查询结果的返回值类型
package cn;
/**
* @author Bonbons
* @version 1.0
*/
public class User {
private String id;
private String name;
private String age;
public User(){
}
public User(String id, String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
(5)编写测试类 UserMapperTest
package com.powernode.godbatis.test;
import cn.User;
import org.god.ibatis.core.SqlSession;
import org.god.ibatis.core.SqlSessionFactoryBuilder;
import org.god.ibatis.utiils.Resources;
import org.junit.Test;
/**
* @author Bonbons
* @version 1.0
*/
public class UserMapperTest {
@Test
public void testInsertUser(){
//创建会话
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml")).openSession();
//执行SQL
User user = new User("111", "孙悟空", "20");
sqlSession.insert("user.insertUser", user);
//提交事务、关闭连接
sqlSession.commit();
sqlSession.close();
}
@Test
public void testSelectOne(){
//创建会话
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml")).openSession();
//执行SQL
Object user = sqlSession.selectOne("user.selectById", 1);
System.out.println(user);
//关闭连接
sqlSession.close();
}
}
- 对于这两个方法我们分开进行测试【使用了junit进行单元测试】
- 测试插入方法,原数据库中只有一条记录
运行结果,孙悟空的信息成功添加进去
- 测试根据id查询数据,我们就查id为1的用户,查询结果如下
点击下载godbatis的jar包(阿里云盘): godbatis-1.0-SNAPSHOT.jar