运行环境
JDK :17
IntelliJ IDEA : 2022.3
Datagrip64: 2022.2
前提工作
- 首先在datagrip中创建一个新的schema:
hand-write-mybatis
,方便我们后续做测试使用~
表结构如下:
create table `hand-write-mybatis`.user
(
id int auto_increment
primary key,
username varchar(255) collate utf8_bin null
);
项目结构
项目类继承关系:
main结构:
java
com.bruce.mapper
:用户自定义的Mapper接口(UserMapper)
com.bruce.pojo
:用户自定义的对象(User)
org.apache.ibatis.configuration
:MyBatis框架的SQL配置封装类
org.apache.ibatis.executor
:SQl语句执行器
org.apache.ibatis.io
:获取一个类加载器
org.apache.ibatis.session
:SqlSessionFactory和SqlSession的实现
resources
mappers
:存放的是用户自定义的Mapper.xml文件
test结构:
java
com.bruce.dao
:回顾动态代理,测试生成一个代理对象
com.bruce.test
:最终的一些测试类
resources
目前只存放了测试Dom4j解析的book.xml
三个重要的XML文件:
UserMapper.xml
:用户自定义的Mapper文件mybatis-config.xml
: MyBatis的配置文件book.xml
: 测试 Dom4j解析功能的xml文件 (非必须)
项目流程
MyBatis运行总流程:
- 读取配置:读取MyBatis的核心配置文件:
mybatis-config.xml
,MyBatis针对每个接口还有一个Mapper映射文件:UserMapper.xml
。底层使用XML解析技术读取配置文件(本项目使用的是Dom4j技术),封装了Configuration
对象。加载MyBatis的配置文件,返回inputstream
字节流,使用SqlSessionFactoryBuilder().build()
方法构建SqlSessionFactory
,通过SqlSessionFactory
获取SqlSession
对象,SqlSession对象.getMapper()
底层使用JDK动态代理生成了接口实现类,也就是代理类对象! - 加载Mapper:解析XML文件,获取SQL语句,并且映射到
Configuration
对象中的mapperStatemengMap
中。 - 构造SqlSession:通过传入的
Configuration
构造一个SqlSessionFactory
,并传入SqlSessionFactory
中。 - 执行SQL:
SimpleExecutor
开始执行,根据id取出一个MappedStatement
对象并执行,然后执行SQL语句。 - 返回结果:一条条取出来交给
ResultSet
结果集中,然后遍历ResultSet
每行取出的值封装到objects列表中,最后返回list。 - 测试框架:在TestMybatis测试类中测试,执行成功后前往数据库查看。
注意:整体的流程最好自己debug一遍,印象会更深刻!
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>Handwriting-MyBatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!--Dom4j依赖,比较流行的解析XML工具-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--XPath(XML Path Language)的解析和查询库-->
<!--使用XPath表达式来查询和操作XML文档中的节点,从而实现对XML数据的处理和分析-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
</dependencies>
<!--控制JDK版本,防止每次refresh maven时自动跳转到JDK5-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
main
com.bruce.mapper包:
UserMapper
接口:
package com.bruce.mapper;
import com.bruce.pojo.User;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: TODO
* @Version: 1.0
**/
public interface UserMapper {
List<User> list();
User findById(User user);
Integer insert(User user);
Integer updateUser(User user);
Integer delete(User user);
}
com.bruce.pojo包:
User
类:
package com.bruce.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: TODO
* @Version: 1.0
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
/**
* 根据对象的属性名与数据库表的列名进行自动映射。如果属性名和列名一致,可以减少配置和映射的工作量。
* 尽管可以通过一些框架提供的注解或配置来指定属性和列的映射关系,但是保持一致性通常是推荐的做法,因为它能够简化代码,并使代码更易于理解。
*/
private Integer id;
private String username;
}
org.apache.ibatis.configuration包:
BoundSql类:作为SQL的封装
package org.apache.ibatis.configuration;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: 作为SQL的封装
* @Version: 1.0
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoundSql {
//要执行的SQL语句,也就是转换完毕之后的SQL语句
private String sqlText;
// 执行SQL语句参数的集合
private List<String> parameterMappingList = new ArrayList<>();
}
Configuration
类:MyBatis框架的SQL配置封装类,后期使用Dom4j进行解析
package org.apache.ibatis.configuration;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: MyBatis框架的SQL配置封装类,后期使用Dom4j进行解析
* @Version: 1.0
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Configuration {
/**
* 数据源
*/
private DataSource dataSource;
/**
* 封装UserMapper.xml文件中的SQL语句
*/
Map<String,MappedStatement> mapperStatemengMap = new ConcurrentHashMap<String,MappedStatement>();
}
MappedStatement
类:MyBatis-config.xml配置文件的映射类,封装UserMapper.xml文件解析之后的SQL语句信息,底层使用Dom4j进行解析!
package org.apache.ibatis.configuration;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: MyBatis-config.xml配置文件的映射类,封装UserMapper.xml文件解析之后的SQL语句信息,底层使用Dom4j进行解析!
* @Version: 1.0
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MappedStatement {
/**
* id标识
*/
private String id;
/**
* SQL语句的返回值
*/
private String resultType;
/**
* 参数类型
*/
private String parameterType;
/**
* SQL语句
*/
private String sql;
/**
* SQL语句的类型(UserMapper标签里的)
*/
private String sqlType;
}
XmlConfigBuilder
类:解析MyBatis核心配置文件(mybatis-config.xml)
package org.apache.ibatis.configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.io.Resources;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: 解析MyBatis核心配置文件(mybatis-config.xml)
* @Version: 1.0
**/
public class XmlConfigBuilder {
/**
* 配置数据封装对象
*/
private Configuration configuration;
/**
* 有参的构造方法
*/
public XmlConfigBuilder(Configuration configuration) {
this.configuration = configuration;
}
public Configuration parseMyBatisConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
List<Element> propertyList = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : propertyList) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name,value);
}
// 初始化数据库连接池,这个地方导入依赖,不需要手写
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
comboPooledDataSource.setUser(properties.getProperty("username"));
comboPooledDataSource.setPassword(properties.getProperty("password"));
// 设置数据源
configuration.setDataSource(comboPooledDataSource);
// MyBatis的核心配置文件中,映射 XxxMapper.xml文件
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String resource = element.attributeValue("resource");
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsStream);
}
return configuration;
}
}
XmlMapperBuilder
类:使用Dom4j解析 UserMapper.xml配置文件
package org.apache.ibatis.configuration;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: 使用Dom4j解析 UserMapper.xml配置文件
* @Version: 1.0
**/
public class XmlMapperBuilder {
/**
* 配置数据封装对象
*/
private Configuration configuration;
/**
* 有参的构造方法
*/
public XmlMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
/**
* 传入配置文件的字节流,解析配置文件
*/
public void parse(InputStream inputStream) throws DocumentException {
SAXReader saxReader = new SAXReader();
// XML文档对象
Document document = saxReader.read(inputStream);
// 获取文档的根节点
Element rootElement = document.getRootElement();
// 获取根节点的属性
Attribute attributeNamespace = rootElement.attribute("namespace");
// com.bruce.mapper.UserMapper
String namespace = attributeNamespace.getValue();
//xpath解析,解析XML配置文件,获取所有查询相关配置节点
List<Element> selectList = rootElement.selectNodes("//select");
//xpath解析,解析XML配置文件,获取所有新增相关配置节点
List<Element> insertList = rootElement.selectNodes("//insert");
//xpath解析,解析XML配置文件,获取所有更新相关配置节点
List<Element> updateList = rootElement.selectNodes("//update");
//xpath解析,解析XML配置文件,获取所有删除相关配置节点
List<Element> deleteList = rootElement.selectNodes("//delete");
List<Element> allList = new ArrayList<>();
allList.addAll(selectList);
allList.addAll(insertList);
allList.addAll(updateList);
allList.addAll(deleteList);
for (Element element : allList) {
String id = element.attributeValue("id");
// 获取返回值类型
String resultType = element.attributeValue("resultType");
// 获取输入参数类型
String parameterType = element.attributeValue("parameterType");
// 获取每个mapper节点中的SQL语句
String sqlText = element.getTextTrim();
// 封装对象
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParameterType(parameterType);
mappedStatement.setSql(sqlText);
mappedStatement.setSqlType(element.getName());
String key = namespace + "." + id;
configuration.getMapperStatemengMap().put(key,mappedStatement);
}
}
}
org.apache.ibatis.executor包:
Executor接口:SQl语句执行器
package org.apache.ibatis.executor;
import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.MappedStatement;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: SQL语句执行器
* @Version: 1.0
**/
public interface Executor {
<E>List<E> query(Configuration configuration, MappedStatement mappedStatement,Object... params) throws Exception;
}
SimpleExecutor
类:SQl语句执行器的实现类
package org.apache.ibatis.executor;
import org.apache.ibatis.configuration.BoundSql;
import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.MappedStatement;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: SQl语句执行器
* @Version: 1.0
**/
public class SimpleExecutor implements Executor {
/**
* 执行SQL核心方法,最终执行JDBC的方法
*
* @param configuration
* @param mappedStatement
* @param params
* @param <E>
* @return
*/
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
// 1.获取数据库连接
Connection connection = configuration.getDataSource().getConnection();
// 2.获取要执行的SQL语句
String sql = mappedStatement.getSql(); // 原始的SQL语句
BoundSql boundSql = this.getBoundSql(sql); // 解析后的SQL语句
System.out.println("要执行的SQL语句是:" + boundSql.getSqlText());
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
// 设置参数
String parameterType = mappedStatement.getParameterType(); // com.bruce.pojo.User
Class<?> parameterTypeClass = this.getClassType(parameterType);
// 获取SQL语句参数集合
List<String> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
String content = parameterMappingList.get(i);
// 暴力反射
Field declaredField = parameterTypeClass.getDeclaredField(content);
declaredField.setAccessible(true);
// 取出参数
Object data = declaredField.get(params[0]);
System.out.println("参数" + content + " SQL语句的参数值:" + data);
preparedStatement.setObject(i + 1, data);
}
// 执行SQL
ResultSet resultSet = null;
if ("select".equals(mappedStatement.getSqlType())) {
// 查询
resultSet = preparedStatement.executeQuery();
} else {
// 增删改
Integer result = preparedStatement.executeUpdate();
List<Integer> resultList = new ArrayList<>();
resultList.add(result);
return (List<E>) resultList;
}
// 获取返回值类型
String returnType = mappedStatement.getResultType();
Class<?> returnTypeClass = this.getClassType(returnType);
List<Object> objects = new ArrayList<>();
// 查询的结果集封装
while (resultSet.next()) {
// 调用无参数的构造方法生产对象
Object o = returnTypeClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 字段名字
String columnName = metaData.getColumnName(i);
// 获取值
Object value = resultSet.getObject(columnName);
//属性封装的两种方法
//方法一:使用内省根据数据库中表和实体类的属性和字段对应关系数据封装
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, returnTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o, value);
//方法二:反射给返回对象赋值
//Field declaredField = returnTypeClass.getDeclaredField(columnName);
//declaredField.setAccessible(true);
//declaredField.set(o,value);
}
objects.add(o);
}
return (List<E>) objects;
}
/**
* 根据类的全名称获取Class
*
* @param parameterType
* @return
*/
public Class<?> getClassType(String parameterType) throws ClassNotFoundException {
if (parameterType != null) {
Class<?> aClass = Class.forName(parameterType);
return aClass;
}
return null;
}
Map<Integer, Integer> map = new TreeMap<>();
int findPosition = 0;
// SQL语句的参数
List<String> parameterMappings = new ArrayList<>();
/**
* 完成对#{}的解析工作:
* 1.将#{}使用?占位符代替
* 2.解析出#{}里面的值进行存储
*
* @return 解析后的sql
*/
private BoundSql getBoundSql(String sql) {
this.parserSql(sql);
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
for (Map.Entry<Integer, Integer> entry : entries) {
Integer key = entry.getKey() + 2;
Integer value = entry.getValue();
parameterMappings.add(sql.substring(key, value));
}
for (String s : parameterMappings) {
sql = sql.replace("#{" + s + "}", "?");
}
BoundSql boundSql = new BoundSql(sql, parameterMappings);
return boundSql;
}
/**
* 递归算法 select * from user where id=#{id} and username=#{username}
*
* @param sql
*/
private void parserSql(String sql) {
int openIndex = sql.indexOf("#{", findPosition);
if (openIndex != -1) {
int endIndex = sql.indexOf("}", findPosition + 1);
if (endIndex != -1) {
map.put(openIndex, endIndex);
findPosition = endIndex + 1;
parserSql(sql);
} else {
System.out.println("SQL语句中参数错误..");
}
}
}
}
org.apache.ibatis.io包:
Resources
类:类加载器
package org.apache.ibatis.io;
import java.io.InputStream;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: 类加载器
* @Version: 1.0
**/
public class Resources {
/**
* 根据配置文件的路径,将配置文件加载为字节流形式,存储在内存中
* @param path 配置文件的位置
* @return 返回的字节流
*/
public static InputStream getResourceAsStream(String path) {
// 加载类路径下的配置文件,以字节流形式返回
return Resources.class.getClassLoader().getResourceAsStream(path);
}
}
org.apache.ibatis.session包:
SqlSession接口:SqlSession 对象
package org.apache.ibatis.session;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: SqlSession 对象
* @Version: 1.0
**/
public interface SqlSession {
/**
* 查询所有数据
* @param statemenntId sql语句的唯一id
* @param params 查询sql需要的可变参数
* @return
* @param <E>
* @throws Exception
*/
<E> List<E> selectList(String statemenntId,Object... params) throws Exception;
/**
* 根据条件查询单个对象
*/
<E> E selectOne(String statemenntId,Object... params) throws Exception;
/**
* 新增
*/
<E> E insert(String statemenntId,Object... params) throws Exception;
/**
* 更新
*/
<E> E update(String statemenntId,Object... params) throws Exception;
/**
* 删除
*/
<E> E delete(String statemenntId,Object... params) throws Exception;
/**
* 为mapper层的接口JDK动态代理生成实现类
*/
<T> T getMapper(Class<?> mapperClass) throws Exception;
}
DefaultSqlSession
类:SqlSession的实现类
package org.apache.ibatis.session;
import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.MappedStatement;
import org.apache.ibatis.executor.SimpleExecutor;
import java.lang.reflect.*;
import java.util.List;
import java.util.Map;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: SqlSession的实现类
* @Version: 1.0
**/
public class DefaultSqlSession implements SqlSession {
// 封装的是配置信息
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statemenntId, Object... params) throws Exception {
//SimpleExecutorQuery完成查询
SimpleExecutor simpleExecutor = new SimpleExecutor();
MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>) list;
}
@Override
public <E> E selectOne(String statemenntId, Object... params) throws Exception {
List<Object> objects = this.selectList(statemenntId, params);
if (objects.size() == 1) {
return (E) objects.get(0);
} else if (objects.size() > 1) {
throw new RuntimeException("查询的结果不唯一!");
} else {
throw new RuntimeException("查询的结果为空!");
}
}
@Override
public <E> E insert(String statemenntId, Object... params) throws Exception {
SimpleExecutor simpleExecutor = new SimpleExecutor();
MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
if (list.size()>0) {
return (E) list.get(0);
}else {
return (E) "0";
}
}
@Override
public <E> E update(String statemenntId, Object... params) throws Exception {
SimpleExecutor simpleExecutor = new SimpleExecutor();
MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
if (list.size()>0) {
return (E) list.get(0);
}else {
return (E) "0";
}
}
@Override
public <E> E delete(String statemenntId, Object... params) throws Exception {
SimpleExecutor simpleExecutor = new SimpleExecutor();
MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
if (list.size()>0) {
return (E) list.get(0);
}else {
return (E) "0";
}
}
/**
* 使用JDK动态代理给UserMapper接口生成一个代理类对象
*
* @param mapperClass 字节码
* @param <T>
* @return
* @throws Exception
*/
@Override
public <T> T getMapper(Class<?> mapperClass) throws Exception {
//使用JDK动态代理技术为Mapper接口层生成代理类对象,其实就是实现类,并返回
// 直接new一个接口,匿名内部类的写法
Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// <select id="list" ... >
// 接口中的方法名字 "list"
String methodName = method.getName();
// UserMapper接口的全类名 "com.bruce.mapper.UserMapper"
String className = method.getDeclaringClass().getName();
// 拼接SQL的唯一标识
String statementId = className + "." + methodName;
// 获取方法被调用之后的返回值类型
Type genericReturnType = method.getGenericReturnType();
Map<String, MappedStatement> mapperStatemengMap = configuration.getMapperStatemengMap();
MappedStatement mappedStatement = mapperStatemengMap.get(statementId);
// 根据标签判断SQL类型
if ("insert".equals(mappedStatement.getSqlType())) {
return insert(statementId, args);
} else if ("update".equals(mappedStatement.getSqlType())) {
return update(statementId, args);
} else if ("delete".equals(mappedStatement.getSqlType())) {
return delete(statementId, args);
}
//判断是否进行泛型类型的参数化(返回结果是泛型的话,查询集合)
if (genericReturnType instanceof ParameterizedType) {
List<Object> objects = selectList(statementId, args);
return objects;
} else {
// 查询结果不是泛型
return selectOne(statementId, args);
}
}
});
return (T) instance;
}
}
SqlSessionFactory接口:
package org.apache.ibatis.session;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: SqlSessionFactory
* @Version: 1.0
**/
public interface SqlSessionFactory {
// 获取sqlsession
SqlSession openSession();
}
DefaultSqlSessionFactory类:SqlSessionFactory的实现类
package org.apache.ibatis.session;
import org.apache.ibatis.configuration.Configuration;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: SqlSessionFactory 的实现类
* @Version: 1.0
**/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
SqlSessionFactoryBuilder
类:生成SqlSessionFactory对象
package org.apache.ibatis.session;
import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.XmlConfigBuilder;
import org.dom4j.DocumentException;
import java.beans.PropertyVetoException;
import java.io.InputStream;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: 生成SqlSessionFactory对象
* @Version: 1.0
**/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
// 获取configuration对象
Configuration configuration = new Configuration();
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
xmlConfigBuilder.parseMyBatisConfig(inputStream);
// 创建SqlSessionFactory
DefaultSqlSessionFactory sessionFactory = new DefaultSqlSessionFactory(configuration);
return sessionFactory;
}
}
UserMapper.xml
文件:
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.bruce.mapper.UserMapper">
<!--查询
parameterType="" 输入参数类型
resultType="" 返回参数类型
-->
<select id="list" resultType="com.bruce.pojo.User">
select * from user
</select>
<select id="findById" resultType="com.bruce.pojo.User" parameterType="com.bruce.pojo.User">
select * from user where id = #{id}
</select>
<insert id="insert" resultType="java.lang.Integer" parameterType="com.bruce.pojo.User">
insert into user values (null,#{username})
</insert>
<update id="updateUser" resultType="java.lang.Integer" parameterType="com.bruce.pojo.User">
update user set username=#{username} where id=#{id}
</update>
<delete id="delete" resultType="java.lang.Integer" parameterType="com.bruce.pojo.User">
delete from user where id=#{id}
</delete>
</mapper>
mybatis-config.xml
文件:在这里填入你的数据库信息 !
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!--1.数据库配置信息-->
<dataSource>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hand-write-mybatis"></property>
<property name="username" value="xxxxxx"></property>
<property name="password" value="xxxxxx"></property>
</dataSource>
<!--2.存放mapper.xml的全路径-->
<mapper resource="mappers/UserMapper.xml"></mapper>
</configuration>
test
com.bruce.dao包:
TestUserDao、MyInvercationHandler类:回顾动态代理:测试生成一个代理对象
package com.bruce.dao;
import com.bruce.pojo.User;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: 回顾动态代理:测试生成一个代理对象
* @Version: 1.0
**/
public class TestUserDao {
@Test
public void testUserDao() {
Class c = UserDao.class;
MyInvercationHandler myInvercationHandler = new MyInvercationHandler();
// 生成一个代理对象
UserDao userDao = (UserDao) Proxy.newProxyInstance(c.getClassLoader(), new Class[]{c}, myInvercationHandler);
int i = userDao.add(new User(111, "小黑"));
System.out.println(i > 0 ? "新增成功" : "新增失败");
}
}
/**
* 方法拦截器
*/
class MyInvercationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 调用目标方法的add
if (method.getName().equals("add")) {
System.out.println("执行方法的参数:" + args);
Object o = args[0];
System.out.println(o);
return 1;
} else
return null;
}
}
UserDao接口: 测试动态代理,同上
package com.bruce.dao;
import com.bruce.pojo.User;
public interface UserDao {
Integer add(User user);
}
TestJdbc
类:测试JDBC
package com.bruce.test;
import com.bruce.pojo.User;
import org.junit.Test;
import java.sql.*;
import java.util.ArrayList;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: 回顾JDBC
* @Version: 1.0
**/
/*1.JDBC问题分析
- 数据库连接创建、释放频繁造成系统资源浪费,从而影响性能
- sql语句存在硬编码,造成代码不易维护
- 使用preparedStatement向占有位符号传参数存在硬编码问题
- 对结果解析存在硬编码(查询列名),sgl变化导致解析代码变化
2.问题解决方案
- 数据库频繁创建连接以及释放资源:连接池
- sql语句及参数存在硬编码:配置文件
- 手动解析封装返回结果集:反射、内省
- 这也就解释了前面提的问题,为什么要有 mybatis 框架的存在?这是因为为了解决JDBC 操作存在的这些问题
*/
public class TestJdbc {
@Test
public void testQuery() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
ArrayList<User> users = new ArrayList<User>();
try {
// 注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接对象
// 这里填入自己的数据库信息
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/hand-write-mybatis", "XXXX", "XXXX");
// 获取preparedStatement
preparedStatement = connection.prepareStatement("select * from user");
// 执行SQL到ResultSet
resultSet = preparedStatement.executeQuery();
// 遍历数据
while (resultSet.next()) {
int id = resultSet.getInt("id");
String userName = resultSet.getString("userName");
User user = new User(id, userName);
users.add(user);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
System.out.println("查询到的数据是:" + users);
}
@Test
public void testInsert() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接对象
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/hand-write-mybatis", "root", "385962");
// 获取preparedStatement
preparedStatement = connection.prepareStatement("insert into user values (null,?)");
preparedStatement.setObject(1,"大明");
// 执行SQL到ResultSet
int count = preparedStatement.executeUpdate();
System.out.println(count > 0 ? "新增成功" : "新增失败");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
TestMybatis
类:项目中最重要的测试MyBatis类
package com.bruce.test;
import com.bruce.mapper.UserMapper;
import com.bruce.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: mybatis
* @Version: 1.0
**/
public class TestMybatis {
UserMapper userMapper;
/**
* 在单元测试之前执行
*/
@Before
public void init() {
try {
// 从xml中构建SqlSessionFactory
String resource = "mybatis-config.xml";
// 加载MyBatis的配置文件,返回字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
//框架底层使用JDK动态代理给接口生成实现类对象
userMapper = sqlSession.getMapper(UserMapper.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void testList() {
List<User> users = userMapper.list();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testFindById() {
User u = new User();
u.setId(5);
User user = userMapper.findById(u);
System.out.println(user);
}
@Test
public void testInsert() {
User user = new User();
user.setUsername("小黑");
Integer insert = userMapper.insert(user);
System.out.println(insert > 0 ? "新增成功" : "新增失败");
}
@Test
public void testUpdate() {
User user = new User();
user.setId(6);
user.setUsername("大黑");
Integer update = userMapper.updateUser(user);
System.out.println(update > 0 ? "更新成功" : "更新失败");
}
@Test
public void testDelete() {
User user = new User();
user.setId(2);
Integer delete = userMapper.delete(user);
System.out.println(delete > 0 ? "删除成功" : "删除失败");
}
}
TestXmlConfigBuilder类:测试XmlConfigBuilder
package com.bruce.test;
import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.XmlConfigBuilder;
import org.apache.ibatis.io.Resources;
import org.dom4j.DocumentException;
import org.junit.Test;
import java.beans.PropertyVetoException;
import java.io.InputStream;
/**
* @Author: Juechen
* @Date: 2024/4/22
* @Description: 测试XmlConfigBuilder
* @Version: 1.0
**/
public class TestXmlConfigBuilder {
@Test
public void test1(){
try {
Configuration configuration = new Configuration();
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
xmlConfigBuilder.parseMyBatisConfig(inputStream);
System.out.println("mybatis-config.xml解析完毕!");
System.out.println(configuration);
} catch (DocumentException e) {
throw new RuntimeException(e);
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
TestBook类:测试Dom4j解析book.xml文件
package com.bruce.test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;
import java.util.List;
/**
* @Author: Juechen
* @Date: 2024/4/21
* @Description: 测试Dom4j解析book.xml文件
* @Version: 1.0
**/
public class TestBook {
@Test
public void test1() {
try {
// 创建一个解析器
SAXReader saxReader = new SAXReader();
//获取一个文档对象
Document document = saxReader.read("D:\\Java\\JAVA2022\\Handwriting-MyBatis\\src\\test\\resources\\book.xml");
//获取XML文件的根节点 <books></books>
Element rootElement = document.getRootElement();
System.out.println("根节点的名字是:" + rootElement.getName());
// 获取子节点的集合
List<Element> elements = rootElement.elements();
System.out.println("子节点的个数为:" + elements.size());
System.out.println("-------------------------------------");
// 遍历子节点的集合
for (Element element : elements) {
// 节点属性的对象
//这两行代码完全可以用一行搞定!
// Attribute attribute = element.attribute("id");
// String value = attribute.getValue();
String value = element.attributeValue("id");
System.out.println("id = " + value);
List<Element> children = element.elements();
for (Element child : children) {
// 标签的名字
String tagname = child.getName();
// 标签内部的值
String text = child.getText();
System.out.println(tagname + "=" + text);
}
System.out.println("-------------------------------------");
}
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
}
book.xml文件:
<?xml version="1.0" encoding="UTF-8" ?>
<books>
<book id="b1">
<name>三国演义</name>
<price>59.8</price>
<publish>人民教育出版社</publish>
</book>
<book id="b2">
<name>水浒传</name>
<price>100.5</price>
<publish>机械工业出版社</publish>
</book>
<book id="b3">
<name>Java编程思想</name>
<price>88.8</price>
<publish>机械工业出版社</publish>
</book>
</books>