之前只是简单的走了一波代理还有sql打印, 不过实际应用中还是要去封装JDBC来连接数据库,进行增删查改还有事务管理.
事务管理接口
package com.linnine.mybatis.transaction;
/**
* @author linnine09
* @create 2022/7/25 16:17
*/
public interface Transaction {
/**
* 获取连接
* @return 返回连接
* @throws SQLException
*/
Connection getConnection() throws SQLException;
/**
* 提交事务
* @throws SQLException
*/
void commit() throws SQLException;
/**
* 回滚
* @throws SQLException
*/
void rollback() throws SQLException;
/**
* 关闭连接
* @throws SQLException
*/
void close() throws SQLException;
}
事务工厂 用于生产事务类
package com.linnine.mybatis.transaction;
/**
* 事务工厂
* @author linnine09
* @create 2022/7/25 16:38
*/
public interface TransactionFactory {
/**
* 根据Connection 创建 事务
* @param connection
* @return
*/
Transaction newTransaction(Connection connection);
/**
* 根据数据源和数据隔离级别创建事务
* @param dataSource
* @param level
* @param autoCommit
* @return
*/
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level,boolean autoCommit);
}
jdbc实现事务类
package com.linnine.mybatis.transaction.jdbc;
/**
* 事务管理
* @author linnine09
* @create 2022/7/25 16:21
*/
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;
protected boolean autoCommit;
public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
this.dataSource = dataSource;
this.level = level;
this.autoCommit = autoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection();
connection.setTransactionIsolation(level.getLevel());
connection.setAutoCommit(autoCommit);
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()){
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()){
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null && !connection.getAutoCommit()){
connection.close();
}
}
}
事务级别封装成枚举类
package com.linnine.mybatis.session;
/**
* @author linnine09
* @create 2022/7/25 16:25
*/
@AllArgsConstructor
@Getter
public enum TransactionIsolationLevel {
//包括JDBC支持的5个级别
NONE(Connection.TRANSACTION_NONE),
READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE)
;
private final int level;
}
jdbc事务工厂实现类
package com.linnine.mybatis.transaction.jdbc;
/**
* @author linnine09
* @create 2022/7/25 16:57
*/
public class JdbcTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(Connection connection) {
return new JdbcTransaction(connection);
}
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(dataSource,level,autoCommit);
}
}
基础注册器
package com.linnine.mybatis.type;
/**
* 基础注册器
* @author linnine09
* @create 2022/7/25 16:45
*/
public class TypeAliasRegistry {
private final Map<String,Class<?>> TYPE_ALIASES = new HashMap<>();
public TypeAliasRegistry(){
// 构造函数里注册系统内置的类型别名
registerAlias("string", String.class);
// 基本包装类型
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
}
/**
* 存
* @param alias
* @param value
*/
public void registerAlias(String alias, Class<?> value) {
String key = alias.toLowerCase(Locale.ENGLISH);
TYPE_ALIASES.put(key,value);
}
/**
* 取
* @param string
* @param
* @return
*/
public <T> Class<T> resolveAlias(String string){
String key = string.toLowerCase(Locale.ENGLISH);
return (Class<T>) TYPE_ALIASES.get(key);
}
}
配置类改动,主要是加入了环境变量和上面的注册器, 并在初始化时注册事务工厂和数据源工厂.

环境变量
package com.linnine.mybatis.mapping;
/**
* @author linnine09
* @create 2022/7/25 17:36
*/
@Getter
public class Environment {
private final String id;
private final TransactionFactory transactionFactory;
private final DataSource dataSource;
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
this.id = id;
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
public static class Builder{
private String id;
private TransactionFactory transactionFactory;
private DataSource dataSource;
public Builder(String id) {
this.id = id;
}
public Builder transactionFactory(TransactionFactory transactionFactory){
this.transactionFactory = transactionFactory;
return this;
}
public Builder dataSource(DataSource dataSource){
this.dataSource =dataSource;
return this;
}
public String id(){
return this.id;
}
public Environment build(){
return new Environment(this.id,this.transactionFactory,this.dataSource);
}
}
}
数据源工厂
package com.linnine.mybatis.datasource;
/**
* 数据源工厂
* @author linnine09
* @create 2022/7/25 17:23
*/
public interface DataSourceFactory {
/**
* 设置属性
* @param props
*/
void setProperties(Properties props);
/**
* 获取数据源
* @return
*/
DataSource getDataSource();
}
德鲁伊数据源工厂 就是把属性配置填充到德鲁伊数据源中, 要导个依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
package com.linnine.mybatis.datasource.druid;
/**
* @author linnine09
* @create 2022/7/25 16:58
*/
public class DruidDataSourceFactory implements DataSourceFactory {
private Properties props;
@Override
public void setProperties(Properties props) {
this.props=props;
}
@Override
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(props.getProperty("driver"));
dataSource.setUrl(props.getProperty("url"));
dataSource.setUsername(props.getProperty("username"));
dataSource.setPassword(props.getProperty("password"));
return dataSource;
}
}
整合一下里面的字段,顺便搬了个家 从binding.mapping 搬到mapping下面
package com.linnine.mybatis.mapping;

整合xml文件中每个sql的相关字段
package com.linnine.mybatis.mapping;
/**
* 绑定的SQL,是从SqlSource而来,将动态内容都处理完成得到的SQL语句字符串,其中包括?,还有绑定的参数
* @author linnine09
* @create 2022/7/25 17:54
*/
@Data
@AllArgsConstructor
public class BoundSql {
/**
* sql语句
*/
private String sql;
/**
* 解析占位符
*/
private Map<Integer,String> parameterMappings;
/**
* 参数类型
*/
private String parameterType;
/**
* 返回类型
*/
private String resultType;
}
加入基础注册器,方便xml解析器直接使用

加入了环境配置解析
package com.linnine.mybatis.builder.xml;
/**
* xml 解析类
*
* @author linnine09
*/
public class XMLConfigBuilder extends BaseBuilder {
private Element root;
public XMLConfigBuilder(Reader reader) {
super(new Configuration());
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
root =document.getRootElement();
} catch (DocumentException e) {
e.printStackTrace();
}
}
public Configuration parse() {
try {
// 环境
environmentsElement(root.element("environments"));
//解析映射器
mapperElement(root.element("mappers"));
} catch (Exception e) {
throw new RuntimeException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
return configuration;
}
/**
* 解析默认环境配置
* @param context
* @throws Exception
*/
private void environmentsElement(Element context) throws Exception {
String environment = context.attributeValue("default");
List<Element> environmentList = context.elements("environment");
for (Element e : environmentList) {
String id = e.attributeValue("id");
if (environment.equals(id)){
TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();
Element dataSourceElement = e.element("dataSource");
DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
List<Element> propertyList = dataSourceElement.elements("property");
Properties props = new Properties();
for (Element property : propertyList) {
props.setProperty(property.attributeValue("name"),property.attributeValue("value"));
}
dataSourceFactory.setProperties(props);
DataSource dataSource = dataSourceFactory.getDataSource();
Environment.Builder build = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource)
;
configuration.setEnvironment(build.build());
}
}
}
private void mapperElement(Element mappers) throws Exception {
List<Element> mapperList = mappers.elements("mapper");
for (Element e : mapperList) {
String resource = e.attributeValue("resource");
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element root = document.getRootElement();
String namespace = root.attributeValue("namespace");
List<Element> selectNodes = root.elements("select");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String parameterType = node.attributeValue("parameterType");
String resultType = node.attributeValue("resultType");
String sql = node.getText();
Map<Integer, String> parameter = new HashMap<>();
//匹配参数占位符
Pattern pattern = Pattern.compile("(#\\{(.*?)})");
Matcher matcher = pattern.matcher(sql);
for (int i = 1; matcher.find(); i++) {
String g1 = matcher.group(1);
String g2 = matcher.group(2);
parameter.put(i,g2);
sql = sql.replace(g1, "?");
}
String msId=namespace+"."+id;
String nodeName = node.getName();
SqlCommandType sqlCommandType=SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//解析处理
MappedStatement mappedStatement = new MappedStatement.Builder(configuration,msId,sqlCommandType,new BoundSql(sql,parameter,parameterType,resultType)).build();
//添加解析后的sql
configuration.addMappedStatement(mappedStatement);
}
configuration.addMapper(Resources.classForName(namespace));
}
}
}
改造内部实现
package com.linnine.mybatis.session.defaults;
/**
* sql执行和结果封装
* @author linnine09
* @create 2022/7/25 17:45
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
MappedStatement mappedStatement = configuration.getMappedStatement(statement);
Environment environment = configuration.getEnvironment();
try {
//连接数据库
Connection connection = environment.getDataSource().getConnection();
//获取查询内容
BoundSql boundSql = mappedStatement.getBoundSql();
//填充sql
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
//填充参数 后面应该还得改造 从parameterMapping中取出来遍历放进去
preparedStatement.setLong(1,Long.parseLong(((Object[])parameter)[0].toString()));
//执行查询
ResultSet resultSet = preparedStatement.executeQuery();
//查询出来映射
List<T> objList = resultSet2Obj(resultSet,Class.forName(boundSql.getResultType()));
return objList.get(0);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
List<T> list = new ArrayList<>();
try {
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
while (resultSet.next()){
T obj = (T) clazz.newInstance();
for (int i = 1; i < columnCount; i++) {
Object value = resultSet.getObject(i);
String columnName = metaData.getColumnName(i);
String setMethod ="set"+columnName.substring(0,1).toUpperCase()+ columnName.substring(1);
Method method;
if (value instanceof Timestamp){
method = clazz.getMethod(setMethod, Date.class);
}else {
method = clazz.getMethod(setMethod,value.getClass());
}
method.invoke(obj,value);
}
list.add(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type,this);
}
}
加入数据源环境配置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="DRUID">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
调整mapper的入参和出参类型,xml里面的自己改一下

建库建表
CREATE DATABASE `testdb` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE
USER
(
id bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',
userId VARCHAR(9) COMMENT '用户ID',
userHead VARCHAR(16) COMMENT '用户头像',
createTime TIMESTAMP NULL COMMENT '创建时间',
updateTime TIMESTAMP NULL COMMENT '更新时间',
userName VARCHAR(64),
PRIMARY KEY (id)
)
ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into user (id, userId, userHead, createTime, updateTime, userName) values (1, '10001', '1_04', '2022-04-13 00:00:00', '2022-04-13 00:00:00', '小傅哥');
public static void main(String[] args) throws IOException {
Reader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().builder(reader);
SqlSession sqlSession = sqlSessionFactory.openSqlSession();
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
User user = userDao.queryUserInfoById(1L);
System.out.println(user);
}
报错了,获取数据的连接时失败
java.sql.SQLException: com.mysql.jdbc.Driver
at com.alibaba.druid.util.JdbcUtils.createDriver(JdbcUtils.java:689)
at com.alibaba.druid.pool.DruidDataSource.resolveDriver(DruidDataSource.java:1219)
at com.alibaba.druid.pool.DruidDataSource.init(DruidDataSource.java:888)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1403)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:1399)
at com.alibaba.druid.pool.DruidDataSource.getConnection(DruidDataSource.java:100)
at com.linnine.mybatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:34)
at com.linnine.mybatis.binding.MapperProxy.invoke(MapperProxy.java:48)
at com.sun.proxy.$Proxy0.queryUserInfoById(Unknown Source)
at com.linnine.mybatis.JavaBaseApplicationTest.main(JavaBaseApplicationTest.java:36)
Caused by: java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at com.alibaba.druid.util.JdbcUtils.createDriver(JdbcUtils.java:687)
... 9 more
没有加mysql的依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
重新运行后报错

因为之前建了个User类是空的,所以代理的时候没有找到这个setId的方法,补上
package com.linnine.mybatis.po;
@Data
public class User {
private Long id;
// 用户ID
private String userId;
// 用户名称
private String userName;
// 头像
private String userHead;
// 创建时间
private Date createTime;
// 更新时间
private Date updateTime;
}

查是查出来,但是好像又不完整,找到问题了, 因为在属性填充那里,Date类型用成了sql的date所以转不过去,没仔细看还真容易忽略

最后结果
