今天学到了mybatis的代理,感觉是一种很优秀的思路,来记录下
传统JDBC项目的数据库操作是通过先建一个Dao接口,再在DaoImpl中实现相应方法进而完成操作的(应该是吧…没接触过),我刚学习时在Mybatis中用到了类似的方式
#StudentDao.java
package com.example.springdemo.dao;
import com.example.springdemo.domain.Student;
import java.io.IOException;
import java.util.List;
public interface StudentDao {
Student selectStudentById(int id) throws IOException;
int insertStudent(Student student) throws IOException;
List<Student> selectStudents();
}
#impl.StudentDaoImpl.java
package com.example.springdemo.dao.impl;
import com.example.springdemo.dao.StudentDao;
import com.example.springdemo.domain.Student;
import com.example.springdemo.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class StudentDaoImpl implements StudentDao {
@Override
public Student selectStudentById(int id) {
SqlSession session = MyBatisUtil.getSqlSession();
String sqlId="com.example.springdemo.dao.StudentDao.selectStudentById";
Student student=session.selectOne(sqlId,id);
System.out.println("out:"+student);
session.close();
return student;
}
@Override
public int insertStudent(Student student) {
SqlSession session = MyBatisUtil.getSqlSession();
String sqlId="com.example.springdemo.dao.StudentDao.insertStudent";
int row=session.insert(sqlId,student);
System.out.println("out:"+row);
session.commit();
session.close();
return row;
}
@Override
public List<Student> selectStudents() {
SqlSession session = MyBatisUtil.getSqlSession();
String sqlId="com.example.springdemo.dao.StudentDao.selectStudents";
List<Student> students=session.selectList(sqlId);
for (Student stu:students){
System.out.println("out:"+stu);
}
session.close();
return students;
}
}
然后在我的代码中调用DaoImpl
package com.example.springdemo;
//
//import com.example.springdemo.dao.StudentDao;
//import com.example.springdemo.dao.impl.StudentDaoImpl;
//import com.example.springdemo.domain.Student;
//import com.example.springdemo.utils.MyBatisUtil;
//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.jupiter.api.Test;
//
//import java.io.IOException;
//import java.io.InputStream;
//import java.util.List;
//
public class DaoImplTest {
// @Test
// public void testSelectStudentById() throws IOException {
// StudentDao dao =new StudentDaoImpl();
// dao.selectStudentById(1);
// }
// @Test
// public void testInsertStudent() throws IOException {
// StudentDao dao = new StudentDaoImpl();
// Student student1 = new Student();
// student1.setAge(18);
// student1.setName("quin");
// student1.setId(3);
// student1.setEmail("114514");
// dao.insertStudent(student1);
// }
// @Test
// public void testSelectStudents() throws IOException {
// StudentDao dao =new StudentDaoImpl();
// dao.selectStudents();
// }
}
这里注释掉了…取消注释太麻烦了就凑合看吧
这种方式的确能按照我们想要的方式查询数据库,但mybatis提供了一种可以不需要Impl来实现具体方法,就可以直接调用Dao中的方法
已知Mybatis每个mapper都需要一个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="com.example.springdemo.dao.StudentDao">
<select id="selectStudentById" resultType="com.example.springdemo.domain.Student">
select id,name,email,age from user where id = #{studentId}
</select>
<select id="selectStudents" resultType="com.example.springdemo.domain.Student">
select id,name,email,age from user
</select>
<insert id="insertStudent">
insert into user values(#{id},#{name},#{email},#{age})
</insert>
</mapper>
像这样,我没用去看底层代码,猜测是Mybatis只需要读取这个xml文件,就可以把namespace下的sql语句和该namespace(也就是我们的Dao)联系起来
这也是为什么Mybatis在调用的时候一定需要这样的代码的原因吧
package com.example.springdemo.utils;
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 java.io.IOException;
import java.io.InputStream;
public class MyBatisUtil {
private static SqlSessionFactory factory = null;
static {
String config = "mybatis.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(config);
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
SqlSession session = null;
if (factory != null) {
session = factory.openSession();
}
return session;
}
}
一定要传入一个xml文件,然后用InputStream去读取
当然这不是唯一的一种方式,官网也给出了其他方式的实例
这里先不去考虑这种,专注于利用xml文件的方式
我看的是动力节点的课程(不得不说动力节点yyds),它一开始教的是这样利用Mybatis读数据
package com.example.springdemo;
import com.example.springdemo.domain.Student;
import com.example.springdemo.utils.MyBatisUtil;
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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyTest {
@Test
public void testSelectStudentById() throws IOException {
String config="mybatis.xml";
InputStream inputStream=Resources.getResourceAsStream(config);
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session=factory.openSession();
String sqlId="com.example.springdemo.dao.StudentDao.selectStudentById";
Student student=session.selectOne(sqlId,2);
System.out.println("out:"+student);
session.close();
}
@Test
public void testInsertStudent() throws IOException {
String config="mybatis.xml";
InputStream inputStream=Resources.getResourceAsStream(config);
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session=factory.openSession();
String sqlId="com.example.springdemo.dao.StudentDao.insertStudent";
Student student1 = new Student();
student1.setAge(18);
student1.setName("quin");
student1.setId(3);
student1.setEmail("114514");
int row=session.insert(sqlId,student1);
System.out.println("out:"+row);
session.commit();
session.close();
}
@Test
public void testSelectStudents() throws IOException {
SqlSession session = MyBatisUtil.getSqlSession();
String sqlId="com.example.springdemo.dao.StudentDao.selectStudents";
List<Student> students=session.selectList(sqlId);
for (Student stu:students){
System.out.println("out:"+stu);
}
session.close();
}
}
重点在
String sqlId="com.example.springdemo.dao.StudentDao.selectStudentById";
Student student=session.selectOne(sqlId,2);
sqlId传入的是不是很眼熟,正是我们Dao中的方法名
这里稍微跟进了一下selectOne,它的本质是一种特殊的selectList方法的实现
我们直接看selectList的代码
statement是我们传入的sqlId,底层对它进行了一个getMapper的操作,而这个getMapper就是今天的重点,但我们暂且搁置,看后来我学到的另一种操作数据的方法
package com.example.springdemo;
import com.example.springdemo.dao.StudentDao;
import com.example.springdemo.domain.Student;
import com.example.springdemo.utils.MyBatisUtil;
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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class NewMyTest {
@Test
public void testSelectStudentById() throws IOException {
SqlSession session = MyBatisUtil.getSqlSession();
StudentDao dao = session.getMapper(StudentDao.class);
Student student=dao.selectStudentById(1);
System.out.println("out:"+student);
session.close();
}
@Test
public void testInsertStudent() throws IOException {
SqlSession session = MyBatisUtil.getSqlSession();
StudentDao dao = session.getMapper(StudentDao.class);
Student student1 = new Student();
student1.setAge(18);
student1.setName("quin");
student1.setId(3);
student1.setEmail("114514");
int row=dao.insertStudent(student1);
System.out.println("out:"+row);
session.commit();
session.close();
}
@Test
public void testSelectStudents() throws IOException {
SqlSession session = MyBatisUtil.getSqlSession();
StudentDao dao = session.getMapper(StudentDao.class);
List<Student> students = dao.selectStudents();
for (Student stu:students){
System.out.println("out:"+stu);
}
session.close();
}
}
重点区别在
StudentDao dao = session.getMapper(StudentDao.class);
Student student=dao.selectStudentById(1);
这里我并没有写Impl文件,就直接调用了Dao里的方法,可Dao是接口啊,里面是空的什么都没写,那它是怎么执行我的代码的呢
可以看到这里我们的SqlSession(Mybatis中的重要对象)使用了getMapper方法,作为一个学过不短时间安全的人,第一时间想到的就是去追溯这个方法的源码…
这里就出现了Proxy,也就是我们标题所提到的代理,也是Mybatis的主打卖点,这里调用一篇别人的博客
https://blog.youkuaiyun.com/qq_42046105/article/details/112914184
追溯到newInstance方法
这里从mapperInterface中的得到了Dao这个接口
把断点打在可以得到mapperInterface的地方
发现构造方法中传入的,那么就需要找到哪里第一次调用了这个构造方法传了值,这里再逆推就没有什么收获了,但感觉既然是从xml中获得的,而且应该在一开始,那就该是SqlSessionFactory或者SqlSessionFactoryBuilder了,发现的确,之后借鉴了博客里的思路,找到
org.apache.ibatis.builder.xml.XMLMapperBuilder.bindMapperForNamespace
这里addMapper了
addmapper后调用knownMappers.puts,触发了构造方法,成功初始化
正所谓java一切框架皆基于反射,在这里套用上面博客里的一句话总结就是
Mybatis 会通过 Class#forname 得到 Mapper 接口 Class 对象,生成对应的动态代理对象,核心业务处理都会在
InvocationHandler#invoke 进行处理,Mapper 没有实现类,所有调用 JDBC 等操作都是在 Mybatis
InvocationHandler 实现的。
的确我们上面好像也没有看到它具体实现sql语句的部分…应该都在某个InvocationHandler中,然后通过invoke进行反射调用吧…