文章目录
- 环境搭建
- 第一步:资源⼯具类
- 第二步:定义SqlSessionFactoryBuilder类
- 第三步:定义SqlSessionFactory类
- 第四步:定义JDBC事务管理器
- 第五步:定义数据源类
- 第六步:定义MyMappedStatement类
- 第七步:完善SqlSessionFactory类
- 第八步:完善SqlSessionFactoryBuilder中的build⽅法
- 第九步:添加openSession⽅法获取会话对象
- 第十步:创建会话类MySqlSession
- 测试
环境搭建
首先我们用mybatis的时候有很多XML文件,有核心配置文件和映射文件等等,我们需要解析这些XML文件,可以使用dom4j解析XML⽂件
IDEA中创建Maven模块:我的模块名叫My_mybatis
dom4j可以参考:https://blog.csdn.net/weixin_45832694/article/details/127536145?spm=1001.2014.3001.5502
引入dom4j依赖和mysql驱动依赖,为了测试方便还需要引入junit依赖
<!--dem4j依赖-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!--jaxen依赖-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
⼿写框架之前,我们不知道从哪入手,可以参考之前的mybatis程序
public class MybatisIntroductionTest {
public static void main(String[] args) throws IOException {
// 获取SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 获取SqlSessionFactory对象
InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); // Resources.getResourceAsStream默认就是从类的根路径下开始查找资源。
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is); // 一般情况下都是一个数据库对应一个SqlSessionFactory对象。
// 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(); // 如果使用的事务管理器是JDBC的话,底层实际上会执行:conn.setAutoCommit(false);
// 执行SQL语句,这里insert里的值是XXXMapper.xml
int count = sqlSession.insert("insertCar"); // 返回值是影响数据库表当中的记录条数。
System.out.println("插入了几条记录:" + count);
// 手动提交
sqlSession.commit(); // 如果使用的事务管理器是JDBC的话,底层实际上还是会执行conn.commit();
}
}
第一步:资源⼯具类
可以看出Resources.getResourceAsStream
,Resources是一个工具类,调用静态方法getResourceAsStream,自定义一个工具类MyResouces
/**
* 提供一个工具类,完成类路径的加载
*/
public class MyResouces {
private MyResouces(){//工具类构造方法私有化
}
/**
* 从类路径加载资源
* @param resource 类路径当中的文件名
* @return 指向资源文件的InputStream输入流
*/
public static InputStream getMyResourceAsStream(String resource){
return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
}
}
第二步:定义SqlSessionFactoryBuilder类
参考测试程序,知道要获得SqlSession对象需要创建SqlSessionFactory,而SqlSessionFactory又是通过SqlSessionFactoryBuilder的build方法返回,所以需要定义一个MySqlSessionFactoryBuilder类,提供一个build方法,返回MySqlSessionFactory对象
/**
* MySqlSessionFactory构建器对象,通过bulid方法解析Mybatis的XML配置文件,创建SqlSessionFactory
*/
public class MySqlSessionFactoryBuilder {
public MySqlSessionFactoryBuilder() {
}
/**
* 解析mybatis的XML配置文件,来构建MySqlSessionFactory对象
* @param is 指向配置文件的一个输入流
* @return MySqlSessionFactory
*/
public MySqlSessionFactory bulid(InputStream is){
return null;
}
第三步:定义SqlSessionFactory类
在bulid这个方法中我们需要解析XML文件,而mybatis中的核心配置文件的基本信息有数据源、事务管理器、SQL映射。
事务管理需要用到Connection,而Connection是从配置文件dataSource标签来的,所以在JDBC事务管理器需要数据源属性
所以SqlSessionFactory这个类只需要两个属性一个是事务管理器属性和SQL映射属性
SQL映射在配置文件中可能有多个,所以需要一个集合进行存储。
/**
* MySqlSessionFactory对象:
* 一般一个数据库对应一个MySqlSessionFactory
* 通过MySqlSessionFactory获取SqlSession,开启会话
* 一个MySqlSessionFactory对象能够开启多个SqlSession会话
*/
public class MySqlSessionFactory {
/**
* 事务管理器属性,对应XML配置文件的transactionManager标签
*/
private MyTransaction transactionManager;
/**
* 存放Sql语句的Map集合。
* key存SQL-id
* value存对应的SQL标签对象
* MyMappedStatement是SQL映射的实体类
*/
private Map<String ,MyMappedStatement> mappedStatementMap;
}
第四步:定义JDBC事务管理器
mybatis的事务管理有两种:JDBC事务管理器和MANAGED事务管理器
需要可以随意切换,需要定义一个事务管理器接口,让JDBC事务管理器和MANAGED事务管理器去实现这个接口,实现里面的方法就行了,这里我们只实现JDBC事务管理器
定义⼀个接⼝,然后每⼀个具体的事务管理器都实现这个接⼝。
/**
* 事务管理器接口。提供管理事务的方法
* 所有事务管理器都应该实现这个接口
*/
public interface MyTransaction {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
/**
* 真正开启数据库连接
*/
void openConnention();
/**
* 获取Connention
*/
Connection getConnection();
}
/**
* JDBC事务管理器
*/
public class JdbcTransaction implements MyTransaction{
/**
* 事务管理需要用到Connection,而Connection是从配置文件dataSource标签来的,所以需要数据源属性
* 数据源属性,对应XML配置文件的dataSource标签
*/
private DataSource dataSource;
/**
* 自动提交标志
* true表示自动提交
* false表示不采用自动提交
*/
private boolean autoCommit;
private Connection connection;
@Override
public Connection getConnection() {
return connection;
}
public JdbcTransaction() {
}
public JdbcTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
@Override
public void commit() {
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void rollback() {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void close() {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void openConnention() {
if (connection == null) {
try {
connection = dataSource.getConnection();
connection.setAutoCommit(autoCommit);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
第五步:定义数据源类
mybatis的数据源有三种:UNPOOLED、POOLED以及JNDI
这里只需实现UNPOOLED
实现数据源需要去实现DataSource接口,并实现里面的方法
/**
* 数据源实现类:UNPOOLED
* 不使用连接池,每一次都新建Connection对象。
*/
public class UnPooledDataSource implements DataSource {
private String url;
private String username;
private String password;
public UnPooledDataSource() {
}
/**
* 创建一个数据源对象,通过构造方法注册驱动
* @param driver
* @param url
* @param username
* @param password
*/
public UnPooledDataSource(String driver, String url, String username, String password) {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
第六步:定义MyMappedStatement类
/**
* 只是一个普通的java类,封装了SQL标签
* 一个MyMappedStatement对象对应一个SQL标签
*/
public class MyMappedStatement {
private String sql;//对应SQL配置文件的SQL语句
/**
* 映射结果集类型,只有select标签有这个属性,其他标签没有是null,对应SQL配置文件的resultType属性
*/
private String resultType;
public MyMappedStatement() {
}
public MyMappedStatement(String sql, String resultType) {
this.sql = sql;
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
@Override
public String toString() {
return "MyMappedStatement{" +
"sql='" + sql + '\'' +
", resultType='" + resultType + '\'' +
'}';
}
}
第七步:完善SqlSessionFactory类
/**
* MySqlSessionFactory对象:
* 一般一个数据库对应一个MySqlSessionFactory
* 通过MySqlSessionFactory获取SqlSession,开启会话
* 一个MySqlSessionFactory对象能够开启多个SqlSession会话
*/
public class MySqlSessionFactory {
/**
* 事务管理器属性,对应XML配置文件的transactionManager标签
*/
private MyTransaction transactionManager;
/**
* 存放Sql语句的Map集合。
* key存SQL-id
* value存对应的SQL标签对象
*/
private Map<String ,MyMappedStatement> mappedStatementMap;
public MySqlSessionFactory() {
}
public MySqlSessionFactory(MyTransaction transactionManager, Map<String, MyMappedStatement> mappedStatementMap) {
this.transactionManager = transactionManager;
this.mappedStatementMap = mappedStatementMap;
}
public MyTransaction getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(MyTransaction transactionManager) {
this.transactionManager = transactionManager;
}
public Map<String, MyMappedStatement> getMappedStatementMap() {
return mappedStatementMap;
}
public void setMappedStatementMap(Map<String, MyMappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
}
第八步:完善SqlSessionFactoryBuilder中的build⽅法
开始解析配置文件
1.使用Dom4j创建reader对象
2.通过类加载器获取Myabtis的配置文件输入流,这个已经在工具类封装了,而且是通过参数传过来了。
3.读XML文件,返回一个文档对象document
4.对标签进行处理
4.1获取默认的环境,因为将来有可能配置多个环境
(1)获取environments的default的值
(2)通过defalt获取默认environment
4.2处理默认环境的配置
(1)获取事务管理器标签
(2)获取数据源标签
(3)对数据源和事务管理器进行处理,创建数据源对象和事务管理器对象
5.获取所有的Mapper标签,进行处理,构建map集合
6.最后创建出一个MySqlSessionFactory对象
/**
* MySqlSessionFactory构建器对象,通过bulid方法解析Mybatis的XML配置文件,创建SqlSessionFactory
*/
public class MySqlSessionFactoryBuilder {
public MySqlSessionFactoryBuilder() {
}
/**
* 解析mybatis的XML配置文件,来构建MySqlSessionFactory对象
* @param is 指向配置文件的一个输入流
* @return MySqlSessionFactory
*/
public MySqlSessionFactory bulid(InputStream is){
MySqlSessionFactory factory = null;
MyTransaction transactionManager = null;
Map<String ,MyMappedStatement> mappedStatementMap = null;
DataSource dataSource = null;
try {
//1.使用Dom4j创建reader对象
SAXReader reader = new SAXReader();
//2.通过类加载器获取Myabtis的配置文件输入流,这个已经在工具类封装了
//3.读XML文件,返回一个文档对象document
Document document = reader.read(is);
//4.对标签进行处理
//4.1获取默认的环境,因为将来有可能配置多个环境
//(1)获取environments的default的值
//通过标签匹配路径获取节点,这样就获取到了environments这个节点,返回是Node,Element是Node的子类,方法更多,使用更方便
Element environments = ((Element) document.selectSingleNode("/configuration/environments"));
//通过attributeValue获取default的值
String defalt = environments.attributeValue("default");
//(2)通过defalt获取默认environment
Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='"+defalt+"']");
//4.2处理默认环境的配置
//(1)获取事务管理器标签
//获取environment下的transactionManager标签,element用来获取孩子节点
Element transactionManagerElt = environment.element("transactionManager");
//获取事务管理器的类型
//String transactionType = transactionManagerElt.attributeValue("type");
//(2)获取数据源标签
Element dataSourceElt = (Element) environment.element("dataSource");
//String dataSourceType = dataSourceElt.attributeValue("type");
//(3)对数据源和事务管理器进行处理
//通过数据源标签获取数据源
dataSource = getDataSource(dataSourceElt);
//通过数据源和事务管理器标签,获取事务管理器
transactionManager = getTransactionManager(transactionManagerElt,dataSource);
//5.获取所有的Mapper标签,进行处理
List<Node> mappers = document.selectNodes("//mapper");
//通过mapper标签,处理射信息
mappedStatementMap = getMappedStatement(mappers);
factory = new MySqlSessionFactory(transactionManager,mappedStatementMap);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
/**
* 解析所有的SQLMapper.xml文件,然后构建map集合
* @param mappers 所有mapper的List集合
* @return
*/
private Map<String, MyMappedStatement> getMappedStatement(List<Node> mappers) {
Map<String, MyMappedStatement> mappedStatementMap = new HashMap<>();
mappers.forEach(mapper -> {
Element mapperElt = (Element) mapper;
//获取resource映射路径
String resource = mapperElt.attributeValue("resource");
//通过映射路径解析SQLMapper.xml文件
SAXReader reader = new SAXReader();
try {
Document document = reader.read(MyResouces.getMyResourceAsStream(resource));
Element SQLmapper = ((Element) document.selectSingleNode("mapper"));
String namespace = SQLmapper.attributeValue("namespace");
List<Element> elements = SQLmapper.elements();
elements.forEach(element -> {
String id = element.attributeValue("id");
String sqlid = namespace + "." + id;
String resultType = element.attributeValue("resultType");
//获取文本,去掉前后空格
String sql = element.getTextTrim();
MyMappedStatement myMappedStatement = new MyMappedStatement(sql,resultType);
mappedStatementMap.put(sqlid,myMappedStatement);
});
} catch (DocumentException e) {
e.printStackTrace();
}
});
return mappedStatementMap;
}
/**
* 获取数据源对象
* @param dataSourceElt 数据源标签
* @return
*/
private DataSource getDataSource(Element dataSourceElt) {
DataSource dataSource = null;
Map<String,String> map = new HashMap<>();
List<Element> propertys = dataSourceElt.elements();
propertys.forEach(propertysElt -> {
String name = propertysElt.attributeValue("name");
String value = propertysElt.attributeValue("value");
map.put(name,value);
});
//获取数据源的类型,有可能是小写,所以要把它统一变成大写,trim()去掉前后空格
String dataSourceType = dataSourceElt.attributeValue("type").trim().toUpperCase();
if (Const.UN_POOLED_DATASOURCE.equals(dataSourceType)) {//UNPOOLED
dataSource = new UnPooledDataSource(map.get("driver"),map.get("url"),map.get("username"),map.get("password"));
}else if (Const.POOLED_DATASOURCE.equals(dataSourceType)){//POOLED
dataSource = new PooledDataSource();
}else if (Const.JNDI_DATASOURCE.equals(dataSourceType)){//JNDI
dataSource = new JNDIDataSource();
}
return dataSource;
}
/**
* 获取事务管理器对象
* @param transactionManagerElt 事务管理器标签
* @param dataSource 数据源对象
* @return
*/
private MyTransaction getTransactionManager(Element transactionManagerElt, DataSource dataSource) {
MyTransaction transaction = null;
String transactionType = transactionManagerElt.attributeValue("type");
if (Const.JDBC_TRANSACTION.equals(transactionType)) {//JDBC,默认开启事务
transaction = new JdbcTransaction(dataSource,false);
}else if (Const.MANAGED_TRANSACTION.equals(transactionType)){//MANAGED
transaction = new ManagedTransaction();
}
return transaction;
}
}
第九步:添加openSession⽅法获取会话对象
/**
* 获取SQL会话对象
* @return
*/
public MySqlSession openSession(){
//打开连接
transactionManager.openConnention();
MySqlSession sqlSession = new MySqlSession(this);
return sqlSession;
}
第十步:创建会话类MySqlSession
这里只提供两个方法insert和selectOne
/**
* 专门负责执行SQL语句的会话对象
*/
public class MySqlSession {
MySqlSessionFactory factory;
public MySqlSession() {
}
public MySqlSession(MySqlSessionFactory factory) {
this.factory = factory;
}
//以下就是JDBC代码了,这里只写insert和selectOne
/**
* 执行insert语句,向数据库表当中插入记录
* @param sqlid xml的要执行sql的id
* @param pojo 插入的数据
* @return
*/
public int insert(String sqlid,Object pojo){
int count = 0;
try {
Connection connection = factory.getTransactionManager().getConnection();
String sql = factory.getMappedStatementMap().get(sqlid).getSql();
//把#{}变成占位符?
String newsql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(newsql);
int fromIndex = 0;
int index = 1;
while (true) {
int jingIndex = sql.indexOf("#", fromIndex);
if (jingIndex < 0) {
break;
}
int youkuohaoIndex = sql.indexOf("}", fromIndex);
String properName = sql.substring(jingIndex + 2, youkuohaoIndex).trim();
fromIndex = youkuohaoIndex + 1;
//调用对应的get的方法
String getMethodName = "get" + properName.toUpperCase().charAt(0) + properName.substring(1);
Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);
Object properValue = getMethod.invoke(pojo);
ps.setString(index,properValue.toString());
index++;
}
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return count;
}
/**
* 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句。
* @param sqlId
* @param param
* @return
*/
public Object selectOne(String sqlId, Object param){
Object obj = null;
try {
Connection connection = factory.getTransactionManager().getConnection();
MyMappedStatement mappedStatement = factory.getMappedStatementMap().get(sqlId);
String sql = mappedStatement.getSql();
String newsql = sql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(newsql);
// 给占位符传值
ps.setString(1, param.toString());
// 查询返回结果集
ResultSet rs = ps.executeQuery();
// 要封装的结果类型。
String resultType = mappedStatement.getResultType();
// 从结果集中取数据,封装java对象
if (rs.next()) {
// 获取resultType的Class
Class<?> resultTypeClass = Class.forName(resultType);
// 调用无参数构造方法创建对象
obj = resultTypeClass.newInstance(); // Object obj = new User();
/*
解决问题的关键:将查询结果的列名作为属性名。
列名是id,那么属性名就是:id
列名是name,那么属性名就是:name
*/
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 0; i < columnCount; i++) {
String propertyName = rsmd.getColumnName(i + 1);
// 拼接方法名
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
// 获取set方法
Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
// 调用set方法给对象obj属性赋值
setMethod.invoke(obj, rs.getString(propertyName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
public void commit(){
factory.getTransactionManager().commit();
}
public void rollback(){
factory.getTransactionManager().rollback();
}
public void close(){
factory.getTransactionManager().close();
}
}
测试
数据库创建一个表,这个表只能是Varchar类型,因为手写的这个Mybatis框架没有进行自动类型的推断,对传过来的数据统一看作是字符串处理
配置文件
<?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">
<!--这里只能是JDBC,其他没有实现-->
<transactionManager type="JDBC"/>
<!--这里只能是UNPOOLED,其他没有实现-->
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydbtest"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="DeptMapper.xml"/>
</mappers>
</configuration>
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dept">
<!--id是这条语句的唯一标识。这个id就代表了这条SQL语句-->
<insert id="insertDept">
insert into t_mybatis
value(#{deptno},#{dname},#{loc})
</insert>
<select id="selectById" resultType="com.pojo.Dept">
select * from t_mybatis where deptno=#{deptno}
</select>
</mapper>
实体类
public class Dept {
//private deptno;
private String deptno;
private String dname;
private String loc;
public Dept() {
}
public Dept(String deptno, String dname, String loc) {
this.deptno = deptno;
this.dname = dname;
this.loc = loc;
}
/* public Integer getDeptno() {
return this.deptno;
}*/
/*public void setDeptno(Integer deptno) {
this.deptno = deptno;
}*/
@Override
public String toString() {
return "Dept{" +
"deptno='" + deptno + '\'' +
", dname='" + dname + '\'' +
", loc='" + loc + '\'' +
'}';
}
public String getDeptno() {
return deptno;
}
public void setDeptno(String deptno) {
this.deptno = deptno;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
}
测试程序
@Test
public void testInsert(){
MySqlSessionFactoryBuilder sqlSessionFactoryBuilder = new MySqlSessionFactoryBuilder();
MySqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bulid(MyResouces.getMyResourceAsStream("mybatis-config.xml"));
MySqlSession sqlSession = sqlSessionFactory.openSession();
Dept dept = new Dept("10","销售部","北京");
sqlSession.insert("dept.insertDept",dept);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testSelectOne(){
MySqlSessionFactoryBuilder sqlSessionFactoryBuilder = new MySqlSessionFactoryBuilder();
MySqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.bulid(MyResouces.getMyResourceAsStream("mybatis-config.xml"));
MySqlSession sqlSession = sqlSessionFactory.openSession();
Object o = sqlSession.selectOne("dept.selectById", "10");
System.out.println(o);
sqlSession.commit();
sqlSession.close();
}
insert测试,测试成功通过
数据库数据插入成功
selectOne测试,测试成功,显示数据