本文主要内容摘抄自架构师教程之手写MyBatis框架【完】
虽然感觉他们有点不地道,程序关键代码一闪而过不提供源码,视频中间频繁插入广告,但也算让我学到了点东西,在此表示感谢。
建议别看这篇文章,代码可以看看,文章太乱了,别看,拿着我的代码去看视频吧。代码可以点击这里下载。
手写一个简易版的MyBatis框架:
1.读取mybatis-config.xml配置文件
2.构建SqlSessionFactory
3.打开SqlSession
4.获取Mapper接口对象
5.调用Mapper接口对象的方法操作数据库
准备
MyBatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
我们的目的是手写一个简易版MyBatis,我们不需要支持MyBatis的缓存、动态SQL、高级映射,只要能把Mapper文件与Mapper接口相关联并执行Mapper中定义的SQL即可。
项目开始之前我们先抽取下MyBatis执行的大致过程。
MyBatis大致执行流程
一个普通的MyBatis项目大致由Mapper文件、Mapper接口、MyBatis配置文件组成,如下为MyBatis常用形式:
public void testMybatis () throws IOException {
// 获取配置文件输入流
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 通过SqlSessionFactoryBuilder的build()方法创建SqlSessionFactory实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 调用openSession()方法创建SqlSession实例
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取UserMapper代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 执行Mapper方法,获取执行结果
List<UserEntity> userList = userMapper.listAllUser();
System.out.println(JSON.toJSONString(userList));
}
获取配置文件
这个没啥好说的,读取配置文件
构建SqlSessionFactory
看这个类的名字就知道这个接口可以用于构建SqlSession,这是一个接口,它有一个默认实现DefaultSqlSessionFactory,其维护了Configuration对象:
// 将XML配置文件构建为Configuration
private final Configuration configuration;
Configuration类十分重要,代表着MyBatis的配置项:
它的Environment中的DataSource属性则保存着数据源,比如用户名、密码、URL等
示例代码通过SqlSessionFactoryBuilder的build方法读取配置文件同时将Configuration构建完成。
不断debug可以看到,SqlSessionFactoryBudiler的build方法会执行XMLConfigBuilder的parse方法,这个方法返回Configuration。
parse方法解析配置文件configuration下的所有信息。
进入parse方法可以后又调用parseConfiguration方法,这里有一个重要的mappersElement方法,mappersElement方法解析配置文件里的mappers信息,通常这包含Mapper映射文件。
mappers解析完成后通过Configuration的addMapper方法加入到Configuraiton维护的MapperRegistry mapperRegistry 。
调用栈:
获取SqlSession
SqlSessionFactory构建完成后可以构建SqlSession了,SqlSession是MyBatis暴露给外部使用的统一接口层。它有一个默认实现DefaultSqlSession:
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
...
}
它持有Configuration和Executor的引用,Executor是真正操作数据库的接口。
SqlSessionFactory的openSession方法最后返回的正是DefaultSqlSession。
获取Mapper接口
显然,Mapper接口不可能一个个实现,所以MyBatis采用JDK自带的动态代理生成Mapper接口的代理对象并返回。
public class MapperProxy<T> implements InvocationHandler, Serializable
MapperProxy里的invoke方法就是具体的拦截逻辑。
MyBatis通过MappedStatement类描述Mapper的信息,包括命名空间、ID(每条SQL命令都有唯一ID,在SqlSession中执行哪条SQL命令由ID指定)、属性类型、返回值类型、SQL语句等。
MappedMethod则封装了Mapper接口的方法,invoke方法内通过调用MapperMethod的execute执行SQL。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 其中command为MapperMethod构造是创建的SqlCommand对象
// 获取SQL语句类型
switch (command.getType()) {
case INSERT: {
// 获取参数信息
Object param = method.convertArgsToSqlCommandParam(args);
// 调用SqlSession的insert()方法,然后调用rowCountResult()方法统计行数
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
...
}
更加详细的过程可以看看这篇文章。
项目准备
以下为我们可能会用到的类:
- MyConfiguration 封装配置文件的配置信息
- MyEnvironment 封装数据源信息,其实可以直接将数据源信息放MyConfiguration中
- MyMapperProxy Mapper接口的代理对象
- MyXMLParser XML文件解析
- MySqlSession 封装与数据库打交道方法的类
- MySqlSessionFactory 构建MySqlSession
- MyExecutor 执行具体的查询、处理结果集
大致步骤如下:
// 1.读取mybatis-config.xml配置文件
// 2.构建SqlSessionFactory
// 3.打开SqlSession
// 4.获取Mapper接口对象
// 5.调用Mapper接口对象的方法操作数据库
下面就正式开始吧。
新建Maven项目,导入JDBC依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
</dependencies>
数据库准备:
create database handwritten_mybatis;
create table handwritten_mybatis.h_user_info
(
id int auto_increment
primary key,
username varchar(20) null,
phone varchar(20) null
);
insert into h_user_info values (default, "Q", "18888888888");
UserInfo类:
public class UserInfo {
private Integer id;
private String username;
private String phone;
//Getter、Setter
}
UserInfoMapper映射接口:
public interface UserInfoMapper {
UserInfo selectByPrimaryKey(Integer id);
}
主测试类:
public class Application {
public static void main(String[] args) {
// 1.读取mybatis-config.xml配置文件
// 2.构建SqlSessionFactory
// 3.打开SqlSession
// 4.获取Mapper接口对象
// 5.调用Mapper接口对象的方法操作数据库
// 业务处理
}
}
MyBatis配置文件mybatis-config.xml,存放在resource目录下
<?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">
<property name="autoCommit" value="true"/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/handwritten_mybatis?userUnicode=true&serverTimezone=GMT%2B8"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserInfoMapper.xml" />
</mappers>
</configuration>
开始
读取配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
构建SqlSessionFactory
先抽取我们的MyConfiguration存储配置文件配置信息:
public class MyConfiguration {
// mybatis-config.xml
private MyEnvironment myEnvironment;
// xxMapper.xml
private Map<String ,MyMapperStatement> myMapperStatementMap;
//Getter、Setter
}
myMapperStatementMap用来存储Mapper文件方法映射,关于MyMapperStatement:
public class MyMapperStatement {
private String namespace;
private String id;
private String parameterType;
private String resultType;
private String sql;
//Getter、Setter
}
用于封装Mapper中的方法,namespace为类的全限定名,id则是方法名,其他三个分别为参数类型、返回值类型以及SQL语句。
MyEnvironment则是用来存储数据源信息的:
public class MyEnvironment {
private String driver;
private String url;
private String username;
private String password;
//Getter、Setter
}
对XML文件进行读取处理不是我们的目的,所以我这里直接给出工具类的代码(我当时弄了蛮久,视频中代码一闪而过而且不全):
public class MyXMLConfigBuilder {
private MyXPathParser parser;
private boolean parsed = false;
public MyXMLConfigBuilder(InputStream in) {
this.parser = new MyXPathParser(in);
}
public MyConfiguration parse() {
if (parsed) {
throw new RuntimeException("Each XMLConfigBuilder can only be used once.");
}
//是否解析过xml配置文件的开关
parsed = true;
//parser 是一个 XPath的解析器,evalNode 评估、评价、计算 节点的值,得到一个封装后的XNode对象
Node dataSourceNode = parser.evalNode("/configuration/environments/environment/dataSource");
NodeList childNodes = dataSourceNode.getChildNodes();
MyConfiguration configuration = new MyConfiguration();
MyEnvironment environment = new MyEnvironment();
for (int i = 0; i<childNodes.getLength();i++) {
Node item = childNodes.item(i);
if (item.getNodeType() == Node.ELEMENT_NODE){
String name = item.getAttributes().getNamedItem("name").getNodeValue();
String value = item.getAttributes().getNamedItem("value").getNodeValue();
if (name.equals("driver")){
environment.setDriver(value);
} else if (name.equals("url")){
environment.setUrl(value);
} else if (name.equals("username")){
environment.setUsername(value);
} else if (name.equals("password")){
environment.setPassword(value);
}
}
}
configuration.setMyEnvironment(environment);
//parser 是一个 XPath的解析器,evalNode 评估、评价、计算 节点的值,得到一个封装后的XNode对象
Node MapperNode = parser.evalNode("/configuration/mappers");
NodeList mapperNodes = MapperNode.getChildNodes();
Map<String, MyMapperStatement> mapperXmlMap = new HashMap<>();
for (int i = 0; i<mapperNodes.getLength();i++) {
Node item = mapperNodes.item(i);
if (item.getNodeType() == Node.ELEMENT_NODE){
String resource = item.getAttributes().getNamedItem("resource").getNodeValue();
InputStream mapperStream = this.getClass().getClassLoader().getResourceAsStream(resource);
this.parser = new MyXPathParser(mapperStream);
Node mapperNode = parser.evalNode("/mapper");
String namespace = mapperNode.getAttributes().getNamedItem("namespace").getNodeValue();
NodeList mapperChildNodes = mapperNode.getChildNodes();
for (int j = 0; j<mapperChildNodes.getLength();j++) {
Node itemj = mapperChildNodes.item(j);
if (itemj.getNodeType() == Node.ELEMENT_NODE){
String sqlId = itemj.getAttributes().getNamedItem("id").getNodeValue();
String parameterType = itemj.getAttributes().getNamedItem("parameterType").getNodeValue();
String resultType = null;
if (itemj.getAttributes().getNamedItem("resultType") != null){
resultType = itemj.getAttributes().getNamedItem("resultType").getNodeValue();
}
String sql = itemj.getTextContent();
// 操作类型(增删改查)
// String sqlType = itemj.getNodeName();
MyMapperStatement mapperXml = new MyMapperStatement();
mapperXml.setId(sqlId);
mapperXml.setNamespace(namespace);
mapperXml.setParameterType(parameterType);
mapperXml.setResultType(resultType);
mapperXml.setSql(sql);
// mapperXml.setSqlType(sqlType);
mapperXmlMap.put(namespace + "." + sqlId,mapperXml);
}
}
}
}
configuration.setMyMapperStatementMap(mapperXmlMap);
//返回解析后的数据封装对象configuration
return configuration;
}
}
public class MyXPathParser {
private Document document;
private boolean validation;
private XPath xpath;
public MyXPathParser(InputStream in) {
//初始化当前类的四个成员变量:validation; entityResolver; variables; xpath;
commonConstructor(true);
//初始化当前类的document对象,创建Document对象
this.document = createDocument(new InputSource(in));
}
/**
* 通用的构造器,初始化当前类的几个成员变量
*
* @param validation
*/
private void commonConstructor(boolean validation) {
this.validation = validation;
//XPath方式的xml解析对象
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
/**
* 评估、评价、计算 节点的值
*
* @param expression
* @return
*/
public Node evalNode(String expression) {
//jdk的xml文件一个节点
Node node = (Node) evaluate(expression, document, XPathConstants.NODE);
if (node == null) {
return null;
}
//XNode是mybatis封装的,代表xml节点的一个对象
return node;
}
/**
* 在一个指定的上下文文档中 评估、评价、计算 一个XPath表达式的值,并返回指定的类型
*
* @param expression
* @param root
* @param returnType
* @return
*/
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new RuntimeException("Error evaluating XPath. Cause: " + e, e);
}
}
/**
* 创建Document对象
*
* @param inputSource
* @return
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
//JDK提供的文档解析工厂对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//设置是否验证
factory.setValidating(validation);
//设置是否支持命名空间
factory.setNamespaceAware(false);
//设置是否忽略注释
factory.setIgnoringComments(true);
//设置是否忽略元素内容的空白
factory.setIgnoringElementContentWhitespace(false);
//是否将CDATA节点转换为文本节点
factory.setCoalescing(false);
//设置是否展开实体引用节点,这里是sql片段引用
factory.setExpandEntityReferences(true);
//创建一个DocumentBuilder对象
DocumentBuilder builder = factory.newDocumentBuilder();
//设置解析mybatis xml文档节点的解析器,也就是上面的XMLMapperEntityResolver
// builder.setEntityResolver(entityResolver);
//设置解析文档错误的处理
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
//解析输入源的xml数据为一个Document文件
return builder.parse(inputSource);
} catch (Exception e) {
throw new RuntimeException("Error creating document instance. Cause: " + e, e);
}
}
}
好的,我们需要达到的目的是获取MySqlSessionFactory:
// 1.读取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.构建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
很明显MySqlSessionFacotryBuilder类用于构建MySqlSessionFactory,我们可以这样:
public class MySqlSessionFactoryBuilder {
public MySqlSessionFactory build(InputStream inputStream) {
// MyBatis配置信息
MyConfiguration myConfiguration = new MyXMLConfigBuilder(inputStream).parse();
return new MySqlSessionFactory(myConfiguration);
}
}
也就是通过MyXMLConfigBuilder类解析输入流并构建MyConfiguration,之后再将MyConfiguration注入MySqlSessionFactory后返回。
构建SqlSession
// 1.读取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.构建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打开SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
关于上文中的MySqlSessionFacotry:
public class MySqlSessionFactory {
private MyConfiguration myConfiguration;
public MySqlSessionFactory(MyConfiguration myConfiguration) {
this.myConfiguration = myConfiguration;
}
public MySqlSession openSession() {
MyExecutor myExecutor = new MyExecutor(myConfiguration);
return new MySqlSession(myConfiguration, myExecutor);
}
}
目的就是构建MySqlSession。
而MySqlSession就是暴露给外部应用使用的接口层:
public class MySqlSession {
private MyConfiguration myConfiguration;
private MyExecutor myExecutor;
public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
this.myConfiguration = myConfiguration;
this.myExecutor = myExecutor;
}
}
MyExecutor则是真正与数据库打交道的类:
public class MyExecutor {
private MyDataSource dataSource;
public MyExecutor(MyConfiguration myConfiguration) {
dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
}
}
所以它需要有查询方法:
public <T> List<T> query(MyMapperStatement mapperStatement, Object param) {
List<T> resultList = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
String sql = mapperStatement.getSql();
String parse = SQLTokenParser.parse(sql);
preparedStatement = connection.prepareStatement(parse);
if (param instanceof Integer) {
preparedStatement.setInt(1, (Integer) param);
} else if (param instanceof Long) {
preparedStatement.setLong(1, (Long) param);
} else if (param instanceof Double) {
preparedStatement.setDouble(1, (Double) param);
} else if (param instanceof String) {
preparedStatement.setString(1, (String) param);
}
resultSet = preparedStatement.executeQuery();
// 处理查询之后的结果
handleResultSet(resultSet, resultList, mapperStatement.getResultType());
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (null != connection) {
// 将连接归还连接池
dataSource.release(connection);
}
}
return resultList;
}
private <T> void handleResultSet(ResultSet resultSet, List<T> resultList, String resultType) {
try {
Class<?> aClass = Class.forName(resultType);
while (resultSet.next()) {
T result = (T) aClass.getConstructor(null).newInstance();
// 把从数据库查询出来的结果集字段的数据要设置到result
ReflectUtil.setProToBeanFromResult(result, resultSet);
resultList.add(result);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
关于ReflectUtil:
public class ReflectUtil {
public static void setProToBeanFromResult(Object entity, ResultSet resultSet) throws SQLException {
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int count = resultSetMetaData.getColumnCount();
Field[] declaredFields = entity.getClass().getDeclaredFields();
for (int i = 0; i < count; i++) {
String columnName = resultSetMetaData.getColumnName(i + 1).replace("_", "").toUpperCase();
for (int i1 = 0; i1 < declaredFields.length; i1++) {
String fieldName = declaredFields[i1].getName().toUpperCase();
if (columnName.equalsIgnoreCase(fieldName)) {
if (declaredFields[i1].getType().getSimpleName().equals("Integer")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getInt(resultSetMetaData.getColumnName(i + 1)));
} else if (declaredFields[i1].getType().getSimpleName().equals("Long")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getLong(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("String")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getString(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("Date")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getDate(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("Boolean")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getBoolean(resultSetMetaData.getColumnName(i + 1)));
}else if (declaredFields[i1].getType().getSimpleName().equals("BigDecimal")) {
setProToBean(entity,declaredFields[i1].getName(),resultSet.getBigDecimal(resultSetMetaData.getColumnName(i + 1)));
}
break;
}
}
}
}
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 | IllegalAccessException e) {
e.printStackTrace();
}
}
}
获取Mapper接口
// 1.读取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.构建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打开SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 4.获取Mapper接口对象
UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
所以我们还需要为我们的MySqlSession类添加一个getMapper方法,以获取Mapper接口的代理:
public <T> T getMapper(Class<T> tClass) {
MyMapperProxy myMapperProxy = new MyMapperProxy(this);
return (T)Proxy.newProxyInstance(tClass.getClassLoader(),
// class com.sun.proxy.$Proxy0 cannot be cast to class top.hellooooo.mapper.UserInfoMapper
// tClass.getInterfaces(),
new Class[]{tClass},
myMapperProxy);
}
MyMapperProxy也就是代理类:
public class MyMapperProxy implements InvocationHandler {
private MySqlSession mySqlSession;
public MyMapperProxy(MySqlSession mySqlSession) {
this.mySqlSession = mySqlSession;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Class<?> returnType = method.getReturnType();
// 如果是集合的子类
if (Collection.class.isAssignableFrom(returnType)) {
return mySqlSession.selectList(args);
} else if (Map.class.isAssignableFrom(returnType)) {
return mySqlSession.selectMap(args);
} else {
String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
// 返回对象数据
return mySqlSession.selectOne(statementKey, args);
}
}
}
invoke方法里调用了mySqlSession的selectOne方法:
public <T> T selectOne(String statementKey, Object[] args) {
// key = namespace . selectId
MyMapperStatement mapperStatement = myConfiguration.getMyMapperStatementMap().get(statementKey);
List<T> query = myExecutor.query(mapperStatement, args != null ? args[0] : null);
if (query != null && query.size() > 1) {
throw new RuntimeException("Too many result..");
} else {
return query.get(0);
}
}
可以看到,最后也还是调用了MyExecutor的query方法,MyExecutor类中有两个方法,一个为query为查询方法,另一个则是处理返回结果的handleResultSet:
public <T> List<T> query(MyMapperStatement mapperStatement, Object param) {
List<T> resultList = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
String sql = mapperStatement.getSql();
String parse = SQLTokenParser.parse(sql);
preparedStatement = connection.prepareStatement(parse);
if (param instanceof Integer) {
preparedStatement.setInt(1, (Integer) param);
} else if (param instanceof Long) {
preparedStatement.setLong(1, (Long) param);
} else if (param instanceof Double) {
preparedStatement.setDouble(1, (Double) param);
} else if (param instanceof String) {
preparedStatement.setString(1, (String) param);
}
resultSet = preparedStatement.executeQuery();
// 处理查询之后的结果
handleResultSet(resultSet, resultList, mapperStatement.getResultType());
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (null != connection) {
// 将连接归还连接池
dataSource.release(connection);
}
}
return resultList;
}
private <T> void handleResultSet(ResultSet resultSet, List<T> resultList, String resultType) {
try {
Class<?> aClass = Class.forName(resultType);
while (resultSet.next()) {
T result = (T) aClass.getConstructor(null).newInstance();
// 把从数据库查询出来的结果集字段的数据要设置到result
ReflectUtil.setProToBeanFromResult(result, resultSet);
resultList.add(result);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
调用接口的方法
// 1.读取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.构建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打开SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 4.获取Mapper接口对象
UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
// 5.调用Mapper接口对象的方法操作数据库
UserInfo userInfo = userInfoMapper.selectByPrimaryKey(1);
可以看到调用了selectByPrimaryKey,然而userInfoMapper是一个接口,很明显没法用,所以JVM会自动调用代理对象的invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Class<?> returnType = method.getReturnType();
// 如果是集合的子类
if (Collection.class.isAssignableFrom(returnType)) {
return mySqlSession.selectList(args);
} else if (Map.class.isAssignableFrom(returnType)) {
return mySqlSession.selectMap(args);
} else {
String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
// 返回对象数据
return mySqlSession.selectOne(statementKey, args);
}
}
测试:
public static void main(String[] args) {
// 1.读取mybatis-config.xml配置文件
InputStream inputStream = Application.class.getResourceAsStream("/mybatis-config.xml");
// 2.构建SqlSessionFactory
MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);
// 3.打开SqlSession
MySqlSession mySqlSession = mySqlSessionFactory.openSession();
// 4.获取Mapper接口对象
UserInfoMapper userInfoMapper = mySqlSession.getMapper(UserInfoMapper.class);
// 5.调用Mapper接口对象的方法操作数据库
UserInfo userInfo = userInfoMapper.selectByPrimaryKey(1);
// 业务处理
System.out.println(userInfo);
}
结果:
UserInfo{id=1, username='Q', phone='18888888888'}