文章目录
前言
Mybatis是现在非常流行的一种持久层框架,接下来实现一下Mybatis的底层架构
一、Mybatis是什么?
mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。
mybatis 通过 xml 或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
二、实现步骤
1.创建项目引入库
引入JDBC库:
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> </dependencies>
2.编写注解
这里实现的是Mybatis注解版,所以先编写Select注解和Param注解
package com.slqf.mybatis;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD})
public @interface Select {
String value();
}
package com.slqf.mybatis;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER})
public @interface Param {
String value();
}
Select注解使用在方法上面,里面编写sql语句,Parm注解使用在方法参数里面,里面是参数的名称
3.编写代理类
先模仿Mybatis简单编写一个UserMapper类
package com.slqf.handle;
import com.slqf.mybatis.Param;
import com.slqf.mybatis.Select;
import java.util.List;
public interface UserMapper {
@Select("select * from user where name = #{name}")
List<User> findUserByName(@Param("name") String name);
@Select("select * from user where name = #{name} and gender = #{gender}")
List<User> findUserByNameAndGender(@Param("name") String name, @Param("gender") String gender);
@Select("select * from user where id = #{id}")
User findUserById(@Param("id") int id);
}
package com.slqf.handle;
public class User {
private String id;
private String name;
private String nick;
private String gender;
private String userModel;
private String isManager;
private String password;
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 getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getUserModel() {
return userModel;
}
public void setUserModel(String userModel) {
this.userModel = userModel;
}
public String getIsManager() {
return isManager;
}
public void setIsManager(String isManager) {
this.isManager = isManager;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", nick='" + nick + '\'' +
", gender='" + gender + '\'' +
", userModel='" + userModel + '\'' +
", isManager='" + isManager + '\'' +
", password='" + password + '\'' +
'}';
}
}
要对这几个查询方法进行增强,运用Proxy代理,创建MapperProxyFactor代理类
public class MapperProxyFactory {
public static <T> T getMapper(Class<T> mapper) {
Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 编写增强方法
}
});
return (T) proxyInstance;
}
}
创建完代理类,就要在其中进行增强方法了,我分为八步来进行
3.1 获取连接
获取需要查询的数据库的连接
//1. 获取连接
String url = "jdbc:mysql://localhost:3306/xiaotan";
String username = "root";
String password = "236547";
Connection conn = DriverManager.getConnection(url, username, password);
3.2 获取参数映射
可以使用invoke方法中的method参数,通过反射获取方法中的形参名称,可是这样获得的方法形参名称不是我们设定的方法中形参的名称,而是arg0、arg1....这样自动生成的名称,为了获取方法参数真正的名称,我们可以使用Param注解来获取
// 2. 获取参数映射
Map<String , Object> paramsValueMapping = new HashMap<>();
Parameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter param = params[i];
String name = param.getAnnotation(Param.class).value();
// 提升代码健壮性,将arg0等的名称也映射进去
paramsValueMapping.put(param.getName(), args[i]);
// 通过Param注解获取的真正的名称
paramsValueMapping.put(name, args[i]);
}
3.3 处理sql语句
要对sql语句进行处理,将Select注解中的sql语句处理成JDBC可以执行的语句
如:select * from user where name = #{name} and gender = #{gender}
替换成:select * from user where name = ? and gender = ?
这样的语句,JDBC的执行sql对象,就可以把“?”替换为真正的参数,处理语句的同时,也要记录“?”对应的参数是什么
package com.slqf.mybatis;
/**
* 存储sql语句?对应的参数名称
*/
public class ParameterMapping {
private String property;
public ParameterMapping(String property) {this.property = property;}
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
}
package com.slqf.mybatis;
import java.util.ArrayList;
import java.util.List;
public class ParameterMappingTokenHandler implements TokenHandler {
// “?”与参数名称映射关系
private List<ParameterMapping> parameterMappings = new ArrayList<>();
@Override
public String handleToken(String content) {
// 顺序存储sql语句每个“?”对应的参数名称
parameterMappings.add(new ParameterMapping(content));
return "?";
}
@Override
public List<ParameterMapping> getParameterMappings() {
// 获取“?”与参数名称映射关系
return parameterMappings;
}
}
package com.slqf.mybatis;
/**
* 处理sql语句工具类,将sql语句中的#{参数}替换为?
*/
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
int start = text.indexOf(openToken);
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
if (expression == null) expression = new StringBuilder();
else expression.setLength(0);
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
expression.append(src, offset, end - offset - 1);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
以上都是处理sql语句所需要的工具类,接下来在代理类中使用来处理sql语句
// 3.处理SQL语句
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
GenericTokenParser genericParser = new GenericTokenParser("#{", "}", handler);
sql = genericParser.parse(sql);
// 获取?与对应参数的映射关系
List<ParameterMapping> parameterMappings = handler.getParameterMappings();
3.4 获取执行sql对象
在这一部分,我们就可以将处理好的sql语句,用sql对象将?替换为查询所用的参数,然后执行sql语句
//4. 获取执行sql对象
PreparedStatement statement = conn.prepareStatement(sql);
// 循环替换每个?对应的参数
for (int i = 0; i < parameterMappings.size(); i++) {
String property = parameterMappings.get(i).getProperty();
Object value = paramsValueMapping.get(property);
// 获取参数的类型
Class<?> type = value.getClass();
// 用对应类型的处理器来替换
typeHandlerMap.get(type).setParameter(statement, i + 1, value);
}
statement.execute();
因为参数的类型可能有所不同,所以不能单纯的用statement.setInt()方法来替换所有的“?”,这就需要在一开始编写和注册不同类型参数的handle处理器
package com.slqf.mybatis;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 数据处理器接口
*/
public interface TypeHandler<T> {
void setParameter(PreparedStatement statement, int i, T value) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
}
package com.slqf.mybatis;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 整数型数据处理器
*/
public class IntegerTypeHandler implements TypeHandler<Integer> {
@Override
public void setParameter(PreparedStatement statement, int i, Integer value) throws SQLException {
statement.setInt(i ,value);
}
@Override
public Integer getResult(ResultSet rs, String columnName) throws SQLException {
return rs.getInt(columnName);
}
}
package com.slqf.mybatis;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 字符型数据处理器
*/
public class StringTypeHandler implements TypeHandler<String> {
@Override
public void setParameter(PreparedStatement statement, int i, String value) throws SQLException {
statement.setString(i, value);
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
}
暂时先编写整数类型和字符串类型的处理器,然后在MapperProxyFactory代理类中用静态代码块进行注册
private static final Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();
static {
// 注册字符串类型
typeHandlerMap.put(String.class, new StringTypeHandler());
// 注册整数类型
typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
}
3.5 获取查询到的结果的列名集合
获取列名集合,在后面处理结果数据时使用
// 5.获取查询到的结果的列名集合
ResultSet resultSet = statement.getResultSet();
ResultSetMetaData resultMetaData = resultSet.getMetaData();
List<String> columnList = new ArrayList<>();
for (int i = 1; i <= resultMetaData.getColumnCount(); i++) {
columnList.add(resultMetaData.getColumnName(i));
}
3.6 获取返回值类型
用反射获取方法的返回值,比如是List还是User,以便创建对象来存储数据后返回
// 6.获取返回值类型
Class resultType = null;
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof Class) {
resultType = (Class) genericReturnType;
} else if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
resultType = (Class) actualTypeArguments[0];
}
3.7 获取setter方法的映射
在获得方法返回类型后,如果创建了对象,例如返回类型为User,创建对象后,要给对象的属性进行赋值,就需要使用user里的set方法,所以需要获取User里的属性与其对应set方法的映射
// 7.获取setter方法映射
Map<String, Method> setterMethodMapping = new HashMap<>();
for (Method declaredMethod : resultType.getDeclaredMethods()) {
if (declaredMethod.getName().startsWith("set")) {
// 获取属性名称
String propertyName = declaredMethod.getName().substring(3);
// 将名称开头字母小写
propertyName = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1);
setterMethodMapping.put(propertyName, declaredMethod);
}
}
3.8 处理结果数据
来到了最后一步,将sql语句查询到的结果数据存入创建的方法返回类型的对象中
// 8.处理结果数据
Object result = null;
List<Object> list = new ArrayList<>();
while (resultSet.next()) {
// 创建方法返回类型对象
Object instance = resultType.newInstance();
for (String columnName : columnList) {
// 根据查询结果的列名获取对应的setter方法
Method setterMethod = setterMethodMapping.get(columnName);
Class clazz = setterMethod.getParameterTypes()[0];
// 使用类型处理器来获取查询结果中列名对应的值
TypeHandler typeHandler = typeHandlerMap.get(clazz);
// 使用反射来执行set方法,给instance的属性赋值
setterMethod.invoke(instance, typeHandler.getResult(resultSet, columnName));
}
list.add(instance);
}
if (method.getReturnType().equals(List.class)) {
result = list;
} else if (!list.isEmpty()) {
result = list.get(0);
}
return result;
4.执行结果
以下是测试代码
package com.slqf.handle;
import com.slqf.mybatis.MapperProxyFactory;
public class Application {
public static void main(String[] args) {
UserMapper userMapper = MapperProxyFactory.getMapper(UserMapper.class);
System.out.println(userMapper.findUserByName("slqf"));
System.out.println(userMapper.findUserByNameAndGender("slqf", "1"));
System.out.println(userMapper.findUserById("2"));
}
}
执行结果如下:
5.代理类完整代码
package com.slqf.mybatis;
import java.lang.reflect.*;
import java.sql.*;
import java.util.*;
public class MapperProxyFactory {
private static final Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();
static {
typeHandlerMap.put(String.class, new StringTypeHandler());
typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
}
public static <T> T getMapper(Class<T> mapper) {
Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//1. 获取连接
Connection conn = getConnection();
Select select = method.getAnnotation(Select.class);
String sql = select.value();
// 2. 获取参数映射
Map<String , Object> paramsValueMapping = new HashMap<>();
Parameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
Parameter param = params[i];
String name = param.getAnnotation(Param.class).value();
paramsValueMapping.put(param.getName(), args[i]);
paramsValueMapping.put(name, args[i]);
}
// 3.处理SQL语句
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
GenericTokenParser genericParser = new GenericTokenParser("#{", "}", handler);
sql = genericParser.parse(sql);
List<ParameterMapping> parameterMappings = handler.getParameterMappings();
//4. 获取执行sql对象
PreparedStatement statement = conn.prepareStatement(sql);
// 循环替换每个?对应的参数
for (int i = 0; i < parameterMappings.size(); i++) {
String property = parameterMappings.get(i).getProperty();
Object value = paramsValueMapping.get(property);
// 获取参数的类型
Class<?> type = value.getClass();
// 用对应类型的处理器来替换
typeHandlerMap.get(type).setParameter(statement, i + 1, value);
}
statement.execute();
// 5.获取查询到的结果的列名集合
ResultSet resultSet = statement.getResultSet();
ResultSetMetaData resultMetaData = resultSet.getMetaData();
List<String> columnList = new ArrayList<>();
for (int i = 1; i <= resultMetaData.getColumnCount(); i++) {
columnList.add(resultMetaData.getColumnName(i));
}
// 6.获取返回值类型
Class resultType = null;
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof Class) {
resultType = (Class) genericReturnType;
} else if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
resultType = (Class) actualTypeArguments[0];
}
// 7.获取setter方法映射
Map<String, Method> setterMethodMapping = new HashMap<>();
for (Method declaredMethod : resultType.getDeclaredMethods()) {
if (declaredMethod.getName().startsWith("set")) {
String propertyName = declaredMethod.getName().substring(3);
propertyName = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1);
setterMethodMapping.put(propertyName, declaredMethod);
}
}
// 8.处理结果数据
Object result = null;
List<Object> list = new ArrayList<>();
while (resultSet.next()) {
// 创建方法返回类型对象
Object instance = resultType.newInstance();
for (String columnName : columnList) {
// 根据查询结果的列名获取对应的setter方法
Method setterMethod = setterMethodMapping.get(columnName);
Class clazz = setterMethod.getParameterTypes()[0];
// 使用类型处理器来获取查询结果中列名对应的值
TypeHandler typeHandler = typeHandlerMap.get(clazz);
// 使用反射来执行set方法,给instance的属性赋值
setterMethod.invoke(instance, typeHandler.getResult(resultSet, columnName));
}
list.add(instance);
}
if (method.getReturnType().equals(List.class)) {
result = list;
} else if (!list.isEmpty()) {
result = list.get(0);
}
return result;
}
});
return (T) proxyInstance;
}
private static Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/xiaotan";
String username = "root";
String password = "236547";
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
}
总结
以上就是Mybatis的简单实现,基于JDBC和动态代理,大量的使用了反射,实现了简单的Mybatis框架,还有很大的扩展空间,如update操作,delete操作等,也是同样的原理,也都可以依葫芦画瓢的实现。