MyBatis是一个持久层框架,使用简单,学习成本较低,所以是我们学习源码的首选框架。
为什么Mybatis可以根据我们的xml去执行相应的sql并将结果封装成实体呢,其实归功于Java中两个很重要的东西:反射、动态代理,这可以使你不去修改程序也不用重新编译,只需要修改xml文件就可以实现sql语句的修改,下面我们深入了解一下Mybatis的启动流程并简单的仿写一个自己的Mybatis
Mybatis层次结构
SqlSession : ,它是 MyBatis 核心 API,主要用来执行命令,获取映射,管理事务。接收开发人员提供 Statement Id 和参数。并返回操作结果。Executor ?:执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成以及查询缓存的维护。StatementHandler ?: 封装了JDBC Statement 操作,负责对 JDBC Statement 的操作,如设置参数、将Statement 结果集转换成 List 集合。ParameterHandler ?: 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。ResultSetHandler ?: 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。TypeHandler ?: 用于 Java 类型和 JDBC 类型之间的转换。MappedStatement ?: 动态 SQL 的封装SqlSource ?: 表示从 XML 文件或注释读取的映射语句的内容,它创建将从用户接收的输入参数传递给数据库的 SQL。Configuration : MyBatis 所有的配置信息都维持在 Configuration 对象之中。
下面开始仿写我们的Mybatis
目录结构
system--|
? ? ? ? BaseExecutor--基础的执行器
? ? ? ? Configuration--配置类,包含MappedStatement
? ? ? ? Executor--执行器接口
? ? ? ? MappedStatement--对应xml中的节点,其中包含方法的全限定名、sql、参数类型等
? ? ? ? XSqlSession--sqlsession工厂,实现接口的实例化及方法的暴露
utils--|
? ? ? ? UnderlineAndHumpUtil--驼峰与下划线互转
? ? ? ? XmlUtil--使用dom4j解析xml
?相关代码
首先是我们的sqlsession工厂中的逻辑,在我们创建一个sqlsession对象的时候需要传入配置文件的目录地址,以此来初始化Configuration对象,之后重写代理方法来执行我们的相关业务,在代理方法中我们可以看到其实数据库操作只需要实现查和改,因为删和增都可以用改来实现
XSqlSession.java代码如下
package com.system;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
/**
* @author xuwei
*/
public class XSqlSession implements InvocationHandler {
private String className;
private final Configuration configuration;
public XSqlSession(String pathName) {
this.configuration = new Configuration(pathName);
}
public Object getMapper(Class cls){
className = cls.getName();
return Proxy.newProxyInstance(
cls.getClassLoader(),
new Class[] { cls },
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//到configuration中去获取方法对应的MappedStatement
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(className+"."+method.getName());
//建立数据库连接
Class.forName(configuration.getDriver());
Connection connection = DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
//建立执行器
BaseExecutor baseExecutor = new BaseExecutor();
Object obj = null;
if (mappedStatement.isQuery()){
if (method.getReturnType().isInstance(new ArrayList<>())){
obj = baseExecutor.queryList(connection,mappedStatement,args,configuration.isUnderlineAndHump());
}else {
obj = baseExecutor.queryOne(connection,mappedStatement,args,configuration.isUnderlineAndHump());
}
}else {
obj = baseExecutor.update(connection,mappedStatement,args);
}
connection.close();
return obj;
}
}
?Configuration.java
Configuration类中包含数据库连接的基本属性以及是否启用下划线和驼峰互转
还包含了我们所有xml中的业务sql,调用了我们的XMLUtil来解析并封装
package com.system;
import com.utils.XmlUtil;
import java.io.File;
import java.util.List;
import java.util.Map;
/**
* @author xuwei
*/
public class Configuration {
private String driver = null;
private String url = null;
private String username = null;
private String password = null;
private boolean underlineAndHump = false;
private Map<String, MappedStatement> mappedStatementMap;
public Configuration(String pathName) {
File file = new File(pathName);
if (file.exists()) {
XmlUtil xmlUtil = new XmlUtil();
Map<String, Object> config = xmlUtil.analysisConfig(file);
this.driver = (String) config.get("driver");
this.url = (String) config.get("url");
this.username = (String) config.get("username");
this.password = (String) config.get("password");
this.underlineAndHump = "true".equals(config.get("underlineAndHump"));
this.mappedStatementMap = xmlUtil.getMappedStatements((List<String>) config.get("mappers"));
}
}
public String getDriver() {
return driver;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean isUnderlineAndHump() {
return underlineAndHump;
}
public Map<String, MappedStatement> getMappedStatementMap() {
return mappedStatementMap;
}
@Override
public String toString() {
return "Configuration{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", underlineAndHump=" + underlineAndHump +
", mappedStatementMap=" + mappedStatementMap +
'}';
}
}
?MappedStatement.java
id为对应接口的全限定名
resultType为返回类型
paramterType为sql的返回值类型
query为标记sql是否为查询语句
sql就是我们具体的业务了
package com.system;
/**
* @author xuwei
*/
public class MappedStatement {
/**
* sql节点的id,对应接口方法名
*/
private final String id;
/**
* sql的返回类型参数
*/
private final String resultType;
/**
* sql的入参类型
*/
private final String parameterType;
/**
* sql是否为查询
*/
private final boolean query;
/**
* sql语句
*/
private final String sql;
public MappedStatement(String id, String resultType, String parameterType, boolean query, String sql) {
this.id = id;
this.resultType = resultType;
this.parameterType = parameterType;
this.query = query;
this.sql = sql;
}
public String getId() {
return id;
}
public String getResultType() {
return resultType;
}
public String getParameterType() {
return parameterType;
}
public boolean isQuery() {
return query;
}
public String getSql() {
return sql;
}
@Override
public String toString() {
return "MappedStatement{" +
"id='" + id + '\'' +
", resultType='" + resultType + '\'' +
", parameterType='" + parameterType + '\'' +
", query=" + query +
", sql='" + sql + '\'' +
'}';
}
}
ExeCutor.java
执行器接口,我们只实现基础执行器,目前不考虑带缓存的高级执行器,里面暴露三个接口:查询多个,对应返回的参数为多个对象的list;查询单个,对应查询单个对象;更新接口,用于增删改
package com.system;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author xuwei
*/
public interface Executor {
/**
* 查询列表接口
*
* @param connection
* @param mappedStatement
* @param args
* @param underlineAndHump
* @return
* @throws Exception
*/
List<Object> queryList(Connection connection, MappedStatement mappedStatement, Object[] args, boolean underlineAndHump) throws Exception;
/**
* 查询单个接口
*
* @param connection
* @param mappedStatement
* @param args
* @param underlineAndHump
* @return
* @throws SQLException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InstantiationException
*/
Object queryOne(Connection connection, MappedStatement mappedStatement, Object[] args, boolean underlineAndHump) throws SQLException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException;
/**
* 修改接口
*
* @param connection
* @param mappedStatement
* @param args
* @return
* @throws SQLException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws IllegalAccessException
*/
int update(Connection connection, MappedStatement mappedStatement, Object[] args) throws SQLException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException;
}
?BaseExecutor.java
封装JDBC,对查询的全过程全权负责
package com.system;
import com.utils.UnderlineAndHumpUtil;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author xuwei
*/
public class BaseExecutor implements Executor {
@Override
public List<Object> queryList(Connection connection, MappedStatement mappedStatement, Object[] args,boolean underlineAndHump) throws ClassNotFoundException, SQLException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
if (mappedStatement.getResultType()==null){
System.out.println("未定义resultType!");
return null;
}
return resultToBean(mappedStatement.getResultType(), initPreparedStatement(connection, mappedStatement, args).executeQuery(),underlineAndHump);
}
@Override
public Object queryOne(Connection connection, MappedStatement mappedStatement, Object[] args,boolean underlineAndHump) throws SQLException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
if (mappedStatement.getResultType()==null){
System.out.println("未定义resultType!");
return null;
}
return resultToBean(mappedStatement.getResultType(), initPreparedStatement(connection, mappedStatement, args).executeQuery(),underlineAndHump).size()==0?null:resultToBean(mappedStatement.getResultType(), initPreparedStatement(connection, mappedStatement, args).executeQuery(),underlineAndHump).get(0);
}
@Override
public int update(Connection connection, MappedStatement mappedStatement, Object[] args) throws SQLException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
return initPreparedStatement(connection, mappedStatement, args).executeUpdate();
}
public PreparedStatement initPreparedStatement(Connection connection, MappedStatement mappedStatement, Object[] args) throws SQLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String regex = "\\{([^}]*)}";
Pattern pattern = Pattern.compile(regex);
String sql = mappedStatement.getSql();
Matcher matcher = pattern.matcher(sql);
List<String> params = new ArrayList<>();
PreparedStatement preparedStatement = null;
//处理sql为预编译形式
if (mappedStatement.getSql().contains("#{")) {
if (mappedStatement.getParameterType() == null) {
//如果采用预编译形式却未定义parameterType则报错
System.out.println("未定义parameterType!");
return null;
}
while (matcher.find()) {
String param = matcher.group(1);
params.add(param);
sql = sql.replace("#{" + param + "}", "?");
}
preparedStatement = connection.prepareStatement(sql);
switch (mappedStatement.getParameterType()) {
case "int":
case "java.lang.Integer":
int num = Integer.parseInt(args[0].toString());
preparedStatement.setInt(1, num);
break;
case "java.lang.String":
String str = args[0].toString();
preparedStatement.setString(1, str);
break;
default:
Class clazz = Class.forName(mappedStatement.getParameterType());
Object obj = args[0];
String name = "";
for (int i = 0; i < params.size(); i++) {
name = params.get(i).substring(0, 1).toUpperCase() + params.get(i).substring(1);
String methodName = "get" + name;
Method methodObj = clazz.getMethod(methodName);
//调用getter方法完成赋值
String value = methodObj.invoke(obj).toString();
preparedStatement.setString(i + 1, value);
}
break;
}
}else {
preparedStatement = connection.prepareStatement(sql);
}
return preparedStatement;
}
public List<Object> resultToBean(String resultType,ResultSet resultSet,boolean underlineAndHump) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, SQLException, InstantiationException, ClassNotFoundException {
Object obj = null;
List<Object> objectList = new ArrayList<>();
while (resultSet.next()){
//反射创建对象
Class clazz = Class.forName(resultType);
obj = clazz.newInstance();
//获取ResultSet数据
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
//遍历实体类属性集合,依次将结果集中的值赋给属性
Field[] fields = clazz.getDeclaredFields();
for(int i = 0; i < fields.length; i++){
Object value = setFieldValueByResultSet(fields[i],resultSetMetaData,resultSet,underlineAndHump);
//通过属性名找到对应的setter方法
String name = fields[i].getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
String methodName = "set"+name;
Method methodObj = clazz.getMethod(methodName,fields[i].getType());
//调用setter方法完成赋值
methodObj.invoke(obj, value);
}
objectList.add(obj);
}
return objectList;
}
public Object setFieldValueByResultSet(Field field,ResultSetMetaData rsmd,ResultSet rs,boolean underlineAndHump){
Object result = null;
try {
int count = rsmd.getColumnCount();
for(int i=1;i<=count;i++){
if(field.getName().equals(underlineAndHump? UnderlineAndHumpUtil.underlineToHump(rsmd.getColumnName(i)):rsmd.getColumnName(i))){
String type = field.getType().getName();
switch (type) {
case "int":
case "java.lang.Integer":
result = rs.getInt(underlineAndHump?UnderlineAndHumpUtil.humpToUnderline(field.getName()):field.getName());
break;
case "java.lang.String":
result = rs.getString(underlineAndHump?UnderlineAndHumpUtil.humpToUnderline(field.getName()):field.getName());
break;
default:
break;
}
}
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
}
UnderlineAndHumpUtil.java
package com.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author xuwei
*/
public class UnderlineAndHumpUtil {
private static final Pattern LINE_PATTERN = Pattern.compile("_(\\w)");
private static final Pattern HUMP_PATTERN = Pattern.compile("[A-Z]");
/**
* 下划线转驼峰
*/
public static String underlineToHump(String str) {
str = str.toLowerCase();
Matcher matcher = LINE_PATTERN.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 驼峰转下划线
*/
public static String humpToUnderline(String str) {
Matcher matcher = HUMP_PATTERN.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
}
?XmlUtil.java
用来解析xml配置文件和xml业务代码
package com.utils;
import com.system.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author xuwei
*/
public class XmlUtil {
/**
* 解析Configuration
*
* @param file
* @return
*/
public Map<String, Object> analysisConfig(File file) {
Map<String, Object> config = new HashMap<>();
SAXReader reader = new SAXReader();
List<String> mappers = new ArrayList<>();
Document document = null;
try {
document = reader.read(file);
} catch (DocumentException e) {
e.printStackTrace();
}
assert document != null;
Element root = document.getRootElement();
List<Element> childElements = root.elements();
for (Element child : childElements) {
if (!child.elements().isEmpty()) {
for (Element c : child.elements()) {
if ("driver".equals(c.attributeValue("name"))) {
config.put("driver", c.attributeValue("value"));
}
if ("url".equals(c.attributeValue("name"))) {
config.put("url", c.attributeValue("value"));
}
if ("username".equals(c.attributeValue("name"))) {
config.put("username", c.attributeValue("value"));
}
if ("password".equals(c.attributeValue("name"))) {
config.put("password", c.attributeValue("value"));
}
if ("underlineAndHump".equals(c.attributeValue("name"))) {
config.put("underlineAndHump", c.attributeValue("value"));
}
if (!"".equals(c.attributeValue("resource")) && c.attributeValue("resource") != null) {
mappers.add(c.attributeValue("resource"));
}
}
}
}
config.put("mappers", mappers);
return config;
}
/**
* 获取mapper
* @param className
* @param mappers
* @return
*/
public String getMapperFile(String className, List<String> mappers) {
for (String mapper : mappers) {
SAXReader reader = new SAXReader();
Document document = null;
try {
document = reader.read(new File(mapper));
} catch (DocumentException e) {
e.printStackTrace();
}
assert document != null;
if (className.equals(document.getRootElement().attributeValue("namespace"))) {
return mapper;
}
}
return null;
}
/**
* 获取mapper
* @param mappers
* @return
*/
public Map<String, MappedStatement> getMappedStatements(List<String> mappers) {
Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
for (String mapper : mappers) {
SAXReader reader = new SAXReader();
Document document = null;
try {
document = reader.read(new File(mapper));
} catch (DocumentException e) {
e.printStackTrace();
}
assert document != null;
Element root = document.getRootElement();
List<Element> childElements = root.elements();
for (Element child : childElements) {
MappedStatement mappedStatement = new MappedStatement(child.attributeValue("id"),
child.attributeValue("resultType"),
child.attributeValue("parameterType"),
"select".equals(child.getName()),
child.getTextTrim());
mappedStatementMap.put(root.attributeValue("namespace") + "." + child.attributeValue("id"), mappedStatement);
}
}
return mappedStatementMap;
}
}
xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<dataSource>
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/user?serverTimezone=GMT&useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useAffectedRows=true&useSSL=false&zeroDateTimeBehavior=convertToNull"/>
<property name="username" value="root"/>
<property name="password" value="Gyj1113.."/>
</dataSource>
<setting>
<property name="underlineAndHump" value="true"/>
</setting>
<mappers>
<mapper resource="src/main/resources/mapper/UserMapper.xml"/>
</mappers>
</configuration>
?到此我们的所有部分就已经结束了,接下来我们测试一下
首先定义实体
package com.entity;
/**
* @author xuwei
*/
public class User {
private Integer id;
private String realName;
private String sex;
private String address;
public String getRealName() {
return realName;
}
public void setRealName(String name) {
this.realName = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", realName='" + realName + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
方法接口
package com.mapper;
import com.entity.User;
import java.util.List;
/**
* @author xuwei
*/
public interface UserMapper {
/**
* 查询所有
*
* @return List
*/
List<User> findAll();
/**
* 查询单个
*
* @param id
* @return User
*/
User findById(Integer id);
/**
* 更新操作
*
* @param user
* @return int
*/
int updateUser(User user);
/**
* 添加操作
*
* @param user
* @return int
*/
int addUser(User user);
/**
* 删除操作
*
* @param id
* @return int
*/
int deleteUser(Integer id);
}
?mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="com.mapper.UserMapper">
<select id="findAll" resultType="com.entity.User">
select * from user;
</select>
<select id="findById" parameterType="int" resultType="com.entity.User">
select * from user where id = #{id};
</select>
<update id="updateUser" parameterType="com.entity.User">
update user set real_name = #{realName} , sex = #{sex} , address = #{address} where id = #{id};
</update>
<insert id="addUser" parameterType="com.entity.User">
insert into user (real_name,sex,address) values (#{realName},#{sex},#{address});
</insert>
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id};
</delete>
<select id="find">
</select>
</mapper>
测试
import com.entity.User;
import com.mapper.UserMapper;
import com.system.XSqlSession;
import org.junit.Test;
public class XWTest {
@Test
public void test() {
XSqlSession xSqlSession = new XSqlSession("src/main/resources/xuwei-mybatis.xml");
UserMapper userMapper = (UserMapper) xSqlSession.getMapper(UserMapper.class);
//先查询一下数据库
System.out.println(userMapper.findAll());
//创建几个对象,保存到数据库中
User user1 = new User();
User user2 = new User();
User user3 = new User();
user1.setRealName("一号");
user1.setSex("男");
user1.setAddress("河北");
user2.setRealName("二号");
user2.setSex("女");
user2.setAddress("广东");
user3.setRealName("三号");
user3.setSex("保密");
user3.setAddress("北京");
userMapper.addUser(user1);
userMapper.addUser(user2);
userMapper.addUser(user3);
//再查询一下
System.out.println(userMapper.findAll());
//查询一个具体的,然后修改
User queryOne = userMapper.findById(1);
System.out.println(queryOne);
queryOne.setSex("保密");
userMapper.updateUser(queryOne);
//看看是否修改成功了
System.out.println(userMapper.findById(1));
//删除一个
userMapper.deleteUser(1);
//查询
System.out.println(userMapper.findAll());
}
}
?测试结果
源码:
qihua99/xuwei-mybatis (github.com)https://github.com/qihua99/xuwei-mybatis
|