如何手写一个简易版MyBatis框架
1.MyBatis简介
-
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
-
也属于ORM框架: Object Relational Mapping
-
用于实现面向对象编程语言里不同类型系统的数据之前的转换
-
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
-
MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs映射成数据库中的记录
2.手写框架我们要做哪些工作
拆解: MyBatis -> MySql
MyBatis底层封装的就是获取和操作数据库的过程
感兴趣的同学可以自己去阅读一下源码,MyBatis核心就是下面三个问题
一、MyBatis是如何获取数据库源
二、Mybatis是如何执行sql语句
三、MyBatis是如何执行数据库
下面用图解方式来看一下MyBatis的原理实现
介绍一下相关核心文件
mybatis-config.xml
XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)
SqlSessionFactoryBuilder.java
SqlSessionFactoryBuilder通过类名就可以看出这个类的主要作用就是创建一个SqlSessionFactory,通过输入mybatis配置文件的字节流或者字符流,生成XMLConfigBuilder,XMLConfigBuilder创建一个Configuration,Configuration这个类中包含了mybatis的配置的一切信息,mybatis进行的所有操作都需要根据Configuration中的信息来进行。
作用域(Scope)和生命周期
可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory接口
sql会话工厂,用于创建SqlSession
作用域(Scope)和生命周期
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建
最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
两种构建方式
使用xml
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
java代码构建
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSession接口
SqlSession是MyBatis的一个重要接口,定义了数据库的增删改查以及事务管理的常用方法。
SqlSession还提供了查找Mapper接口的有关方法
作用域(Scope)和生命周期
- 每个线程都应该有它自己的 SqlSession 实例
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
- 每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。 Mapper接口
承载了实际的业务逻辑,其生命周期比较短,由SqlSession创建,用于将Java对象和实际的SQL语句对应起来。
Mapper接口是指程序员自行定义的一个数据操纵接口,类似于通常所说的DAO接口。跟DAO不同的地方在于Mapper接口只需要程序员定义,不需要程序员去实现,MyBatis会自动为Mapper接口创建动态代理对象。Mapper接口的方法通常与Mapper配置文件中的select、insert、update、delete等XML结点存在一一对应关系。
3.开始实现
我们分5步实现:
1.读取mybatis-config.xml配置文件
2.构建SqlSessionFactory
3.打开sqlsession
4.获取Mapper接口对象
5.调用Mapper接口对象的方法操作数据库 文件结构图如下
3.1 准备工作
新建maven项目,导入依赖:
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>mybatis-write</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</resources>
</build>
</project>
主实现方法测试类:
package com.qinghong;
import com.qinghong.entity.UUserInfo;
import com.qinghong.mapper.UUserInfoMapper;
import com.qinghong.mybatis.session.MySqlSession;
import com.qinghong.mybatis.session.MySqlSessionFactory;
import com.qinghong.mybatis.session.MySqlSessionFactoryBuilder;
import java.io.InputStream;
public class Test {
public static void main(String[] args) {
// 读取mybatis-config.xml配置文件
InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
// 构建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 打开sqlsession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 获取Mapper接口对象
UUserInfoMapper uUserInfoMapper = mySqlSession.getMappwe(UUserInfoMapper.class);
// 操作数据库
UUserInfo uUserInfo = uUserInfoMapper.selectByPrimaryKey(1);
System.out.println(uUserInfo.getId());
}
}
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="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.cj.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/qinghong/mapper/UUserInfoMapper.xml" />
</mappers>
</configuration>
UUserInfoMapper.java
package com.qinghong.mapper;
import com.qinghong.entity.UUserInfo;
public interface UUserInfoMapper {
UUserInfo selectByPrimaryKey(Integer id);
}
UUserInfoMapper.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<mapper namespace="com.qinghong.mapper.UUserInfoMapper">
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultType="com.qinghong.entity.UUserInfo">
select * from account where id = #{id}
</select>
</mapper>
UUserInfo.java
package com.qinghong.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
@NoArgsConstructor
public class UUserInfo {
Integer id;
String name;
Integer money;
}
3.2 构建Session工厂和初始化对象信息
依据MyBatis官方源码,我们也需要创建类似SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession的类;
SqlSessionFactory是生成SqlSession的工厂类,SqlSessionFactory又依赖Configuration
MySqlSessionFactoryBuilder.java
public class MySqlSessionFactoryBuilder {
public MySqlSessionFactory build(InputStream inputStream) {
MyConfiguration myConfiguration = new XMLConfigBuilder(inputStream).parse();
return new MySqlSessionFactory(myConfiguration);
}
}
MySqlSessionFactoryBuilder.java
package com.qinghong.mybatis.session;
import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;
public class MySqlSessionFactory {
private MyConfiguration myConfiguration;
public MySqlSessionFactory() {
}
public MySqlSessionFactory(MyConfiguration myConfiguration) {
this.myConfiguration = myConfiguration;
}
}
根据mybatis-config.xml配置文件的结构我们又需要在里面引入MyEnvironment类型成员变量MyEnvironment,用来存放数据库相关信息,同时引入MyMapperStatement的Map集合变量,对应的一个Mapper.xml里的方法可能对应多个
MyConfiguration.java
package com.qinghong.mybatis.mapping;
import lombok.Data;
import java.util.Map;
@Data
public class MyConfiguration {
// mybatis-config.xml
private MyEnvironment myEnvironment;
// xxMapper.xml
private Map<String , MyMapperStatement> mapperStatement;
}
MyEnvironment.java
package com.qinghong.mybatis.mapping;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyEnvironment {
private String driver;
private String url;
private String username;
private String password;
}
MyMapperStatement.java
package com.qinghong.mybatis.mapping;
import lombok.Data;
@Data
public class MyMapperStatement {
private String namespace;
private String id;
private String parameterType;
private String resultType;
private String sql;
}
引入解析xml文件的两个类:
XMLConfigBuilder.java
package com.qinghong.mybatis.parsing;
import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyEnvironment;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
public class XMLConfigBuilder {
private XPathParser parser;
public XMLConfigBuilder(InputStream inputStream){
this.parser = new XPathParser(inputStream);
}
// 配置文件解析
public MyConfiguration parse(){
Node dataSourceNode = parser.xNode("/configuration/environments/environment");
// 数据源属性配置信息
Properties properties = new Properties();
NodeList childNodes = dataSourceNode.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node item = childNodes.item(i);
if (item.getNodeType() == Node.ELEMENT_NODE){
properties.setProperty(item.getAttributes().getNamedItem("name").getNodeValue(),item.getAttributes().getNamedItem("value").getNodeValue());
}
}
// Mapper映射文件配置信息
Map<String , MyMapperStatement> mapperStatementMap = new ConcurrentHashMap<>();
Node mappersNode = parser.xNode("/configuration/mappers");
NodeList mapperNodeList = mappersNode.getChildNodes();
for (int i = 0; i < mapperNodeList.getLength(); i++) {
Node mapperNode = mapperNodeList.item(i);
if (mapperNode.getNodeType() == Node.ELEMENT_NODE){
// mapper.xml配置
String resource = mapperNode.getAttributes().getNamedItem("resource").getNodeValue();
// 解析mapper.xml文件
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);
this.parser = new XPathParser(inputStream);
Element element = parser.getDocument().getDocumentElement();
String namespace = element.getAttribute("namespace");
NodeList sqlNodeList = element.getChildNodes();
for (int j = 0; j < sqlNodeList.getLength(); j++) {
Node sqlNode = sqlNodeList.item(j);
if (sqlNode.getNodeType() == Node.ELEMENT_NODE){
String id = "";
String resultType = "";
String parameterType = "";
Node idNode = sqlNode.getAttributes().getNamedItem("id");
if (null == idNode){
throw new RuntimeException("sql is null");
}else {
id = sqlNode.getAttributes().getNamedItem("id").getNodeValue();
}
Node resultTypeNode = sqlNode.getAttributes().getNamedItem("resultType");
if (null != resultTypeNode){
resultType = sqlNode.getAttributes().getNamedItem("resultType").getNodeValue();
}
Node parameterTypeNode = sqlNode.getAttributes().getNamedItem("parameterType");
if (null != parameterTypeNode){
parameterType = sqlNode.getAttributes().getNamedItem("parameterType").getNodeValue();
}
String sql = sqlNode.getTextContent();
MyMapperStatement mapperStatement = new MyMapperStatement();
mapperStatement.setId(id);
mapperStatement.setNamespace(namespace);
mapperStatement.setParameterType(parameterType);
mapperStatement.setResultType(resultType);
mapperStatement.setSql(sql);
mapperStatementMap.put(namespace+"."+id,mapperStatement);
}
}
}
}
// 把解析出来的赋值给MyConfiguration
MyConfiguration catConfiguration = new MyConfiguration();
MyEnvironment catEnvironment = new MyEnvironment();
catEnvironment.setDriver(properties.getProperty("driver"));
catEnvironment.setPassword(properties.getProperty("password"));
catEnvironment.setUsername(properties.getProperty("username"));
catEnvironment.setUrl(properties.getProperty("url"));
catConfiguration.setMyEnvironment(catEnvironment);
catConfiguration.setMapperStatement(mapperStatementMap);
return catConfiguration;
}
}
XPathParser.java
package com.qinghong.mybatis.parsing;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;
@Data
@AllArgsConstructor
public class XPathParser {
private XPath xPath;
private final Document document;
public XPathParser(InputStream inputStream){
this.xPath = createXpath();
this.document = createDocument(new InputSource(inputStream));
}
private XPath createXpath() {
XPathFactory factory = XPathFactory.newInstance();
return factory.newXPath();
}
public Document createDocument(InputSource inputSource){
try {
DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder =factory.newDocumentBuilder();
builder.setErrorHandler(new ErrorHandler() {
@Override
public void warning(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
exception.printStackTrace();
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new RuntimeException("error");
}
}
// 根据表达式解析xml节点
public Node xNode(String expression){
Node node = null;
try {
xPath.evaluate(expression,document, XPathConstants.NODE);
} catch (XPathExpressionException e) {
e.printStackTrace();
}
return node;
}
}
执行代码debug可得:
此时xml文件内容已解析完毕;
3.3 构建Session会话
下面是源码中SqlSession结构
configuration可由sqlSessionFactory传入,只需要创建一个执行器类
MySqlSession.java
package com.qinghong.mybatis.session;
import com.qinghong.mapper.UUserInfoMapper;
import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import com.qinghong.mybatis.proxy.MapperProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
public class MySqlSession {
private MyConfiguration myConfiguration;
private MyExecutor myExecutor;
public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
this.myConfiguration = myConfiguration;
this.myExecutor = myExecutor;
}
public <T> T getMappwe(Class<T> clazz) {
MapperProxy mapperProxy = new MapperProxy(this);
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz}, mapperProxy);
}
public <T> T selectOne(String statementKey,Object[] args) {
MyMapperStatement mapperStatement = myConfiguration.getMapperStatement().get(statementKey);
List<T> resultList = myExecutor.query(mapperStatement, args);
if (resultList != null && resultList.size()>1){
throw new RuntimeException("more than one");
}else {
return resultList.get(0);
}
}
public void selectList(Object[] args) {
}
}
在MySqlSessionFactory.java加入新方法:
package com.qinghong.mybatis.session;
import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;
public class MySqlSessionFactory {
private MyConfiguration myConfiguration;
public MySqlSessionFactory() {
}
public MySqlSessionFactory(MyConfiguration myConfiguration) {
this.myConfiguration = myConfiguration;
}
public MySqlSession openSession() {
MyExecutor executor = new MyExecutor(myConfiguration);
return new MySqlSession(myConfiguration,executor);
}
}
3.4 初始化DataSource和Executer
源码运行:
executor中又包含DataSource
MyDataSourceInterface.java
package com.qinghong.pool;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public interface MyDataSourceInterface extends DataSource{
@Override
default Connection getConnection() throws SQLException {
return null;
}
@Override
default Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
default <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
default boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
default PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
default void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
default void setLoginTimeout(int seconds) throws SQLException {
}
@Override
default int getLoginTimeout() throws SQLException {
return 0;
}
@Override
default Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
方法使用default修饰,实现类可以不用实现所有类,简化代码
MyDataSource.java
package com.qinghong.pool;
import com.qinghong.mybatis.mapping.MyEnvironment;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class MyDataSource implements MyDataSourceInterface{
private MyEnvironment myEnvironment;
private List<Connection> pool;
private Connection conn = null;
private static MyDataSource instance = null;
private static final int POOL_SIZE = 15;
private MyDataSource(MyEnvironment myEnvironment){
this.myEnvironment = myEnvironment;
pool = new ArrayList<Connection>(POOL_SIZE);
this.createConnection();
}
public static MyDataSource getInstance(MyEnvironment myEnvironment){
if (instance == null){
instance = new MyDataSource(myEnvironment);
}
return instance;
}
public synchronized Connection getConnection(){
if (pool.size() > 0){
Connection conn = pool.get(0);
pool.remove(conn);
return conn;
}else {
return null;
}
}
/**
* 创建原始数据库连接
*/
public void createConnection(){
for (int i = 0; i < POOL_SIZE; i++) {
try {
Class.forName(myEnvironment.getDriver());
conn = DriverManager.getConnection(myEnvironment.getUrl(), myEnvironment.getUsername(), myEnvironment.getPassword());
pool.add(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
/**
* 用完放回连接池
* @param conn
*/
public synchronized void release(Connection conn){
pool.add(conn);
}
/**
* 关闭链接
*/
public synchronized void closePool(){
for (int i = 0; i < pool.size(); i++) {
conn = pool.get(i);
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
pool.remove(i);
}
}
}
MyExecutor.java
public class MyExecutor {
private DataSource dataSource;
public MyExecutor(MyConfiguration myConfiguration) {
dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
}
}
3.5 动态获取Mapper接口对象和动态调用invoke方法
使用泛型和静态代理技术
代理对象MapperProxy.java
package com.qinghong.mybatis.proxy;
import com.qinghong.mybatis.session.MySqlSession;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
/**
* 代理对象
*/
public class MapperProxy implements InvocationHandler {
private MySqlSession mySqlSession;
public MapperProxy(MySqlSession mySqlSession) {
this.mySqlSession = mySqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> clazz = method.getReturnType();
// 如何返回类型是集合类型的子类
if (Collection.class.isAssignableFrom(clazz)){
// 表示查询多条数据
mySqlSession.selectList(args);
}else if (Map.class.isAssignableFrom(clazz)){
// 表示要返回Map集合
}else {
// 返回对象数据
// statementKey: namespace . selectid
String statementKey = method.getDeclaringClass().getName()+"."+method.getName();
return mySqlSession.selectOne(statementKey,args);
}
return null;
}
}
MySqlSession中新增动态查找接口方法getMapper和查询数据方法selectOne、selectList
package com.qinghong.mybatis.session;
import com.qinghong.mapper.UUserInfoMapper;
import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import com.qinghong.mybatis.proxy.MapperProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
public class MySqlSession {
private MyConfiguration myConfiguration;
private MyExecutor myExecutor;
public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
this.myConfiguration = myConfiguration;
this.myExecutor = myExecutor;
}
public <T> T getMapper(Class<T> clazz) {
MapperProxy mapperProxy = new MapperProxy(this);
return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz}, mapperProxy);
}
/**
* 查询单条数据
* @param statementKey
* @param args
* @param <T>
* @return
*/
public <T> T selectOne(String statementKey,Object[] args) {
// statementKey: namespace . selectid
MyMapperStatement mapperStatement = myConfiguration.getMapperStatement().get(statementKey);
List<T> resultList = myExecutor.query(mapperStatement, args);
if (resultList != null && resultList.size()>1){
throw new RuntimeException("more than one");
}else {
return resultList.get(0);
}
}
public void selectList(Object[] args) {
}
}
MyExecuter.java类中新增查询数据底层query方法和处理结果集方法handlerResultSet
package com.qinghong.mybatis.executor;
import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import com.qinghong.pool.MyDataSource;
import com.qinghong.reflection.ReflectionUtil;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class MyExecutor {
private MyDataSource dataSource;
public MyExecutor(MyConfiguration myConfiguration) {
dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
}
public <T> List<T> query(MyMapperStatement mapperStatement, Object[] args) {
List<T> resultList = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
preparedStatement = connection.prepareStatement(mapperStatement.getSql());
if (args[0] instanceof Integer){
preparedStatement.setInt(1,(Integer) args[0]);
}else if (args[0] instanceof Double) {
preparedStatement.setDouble(1,(Double) args[0]);
}
else if (args[0] instanceof String) {
preparedStatement.setString(1,(String) args[0]);
}
resultSet = preparedStatement.executeQuery();
// 查询后的结果需要处理,转换成一个对象返回
handlerResultSet(resultSet,resultList,mapperStatement.getResultType());
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
if (null != preparedStatement){
try {
preparedStatement.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if (null != connection){
// 把连接归还到连接池,而不是关闭connection
dataSource.release(connection);
}
}
return resultList;
}
/**
* 处理查询出来的ResuleSet
* @param resultSet
* @param resultList
* @param resultType
* @param <T>
*/
private <T> void handlerResultSet(ResultSet resultSet, List<T> resultList,String resultType){
try {
Class<T> clazz = (Class<T>) Class.forName(resultType);
while (resultSet.next()){
// UserInfo
T entity = clazz.newInstance();
// 把从数据库查询出来的结果集字段的数据要设置到entity对象中去
ReflectionUtil.setProToBeanFromResult(entity,resultSet);
resultList.add((T)entity);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (null != resultSet){
try {
resultSet.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
}
处理结果集时handlerResultSet用到反射工具类
ReflectionUtil.java
package com.qinghong.reflection;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Locale;
public class ReflectionUtil {
/**
* 把结果集设置到Baen对象
* @param entity
* @param resultSet
* @throws SQLException
*/
public static void setProToBeanFromResult(Object entity, ResultSet resultSet) throws SQLException {
ResultSetMetaData rsmd = resultSet.getMetaData();
int count = rsmd.getColumnCount();
Field[] decfields = entity.getClass().getDeclaredFields();
for (int i = 0; i < count; i++) {
String columnName = rsmd.getCatalogName(i + 1).replace("_", "").toUpperCase();
for (int j = 0; j < decfields.length; j++) {
String filedName = decfields[j].getName().toUpperCase();
if (columnName.equalsIgnoreCase(filedName)){
if (decfields[j].getType().getSimpleName().equals("Integer")){
setProToBean(entity,decfields[j].getName(),resultSet.getInt(rsmd.getColumnName(i+1)));
}
break;
}
}
}
}
/**
* 属性值设置到Bean中
* @param bean
* @param name
* @param value
*/
private static void setProToBean(Object bean, String name, Object value) {
try {
Field field = bean.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(bean,value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
同时别忘了把Sql语句解析一下,不然就会报如下错误
预编译时这个是
id = #{id,jdbcType = INTEGER}应该替换成#{?},还需要写一个SQLTokenParser方法