1.传统jdbc的代码
2.反射获取注解的sql
3.动态代理执行,
获取方法参数的名字
责任链模式
4.mybatis的独立的一套使用方法
mybatis解析xml,及tomcat解析xml使用的digister
SqlSessionFactory接口(从连接或数据源中获取SqlSession)
sqlSession接口(执行sql,获取mappers,管理事务)
建表
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`name` varchar(25) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
实体类
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
}
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>com.zzhua</groupId>
<artifactId>mybatis-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArgs>
<!---可以支持通过反射拿到接口中的参数的具体名字-->
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.传统JDBC代码
传统的jdbc操作,步骤很多,操作麻烦,很多都是重复的代码
import java.sql.*;
import java.util.ArrayList;
public class JDBCTest {
public static void main(String[] args) {
// 1. 注册驱动
try {
// Class.forName("com.mysql.jdbc.Driver");
// DriverManager.registerDriver(new Driver()); // 自己手动注册mysql的驱动
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
ArrayList<User> list = new ArrayList<>();
try {
// 2. 获取连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mybatis-demo?"+
"serverTimezone=UTC", "zzhua195", "123456");
// 3. 创建statement,用来执行sql
statement = connection.createStatement();
// 4. 返回结果集
resultSet = statement.executeQuery("select * from user");
// 5. 封装对象
while (resultSet.next()) { // 将resultSet的游标向下移动一行,然后读取数据
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
list.add(new User(id, name, age));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
list.forEach(System.out::println);
}
}
上面看上去操作很简单,但是我觉得还是有不少细节,当然还有其它的,后面找些资料具体看下,先挖个坑
- JDBC对操作数据库的整个抽象,如Connection接口,statement接口,ResultSet接口
- 通过反射注册驱动的这个过程,如static静态代码块里注册驱动的代码
- 通过DriverManager获取Connection连接对象的过程
2.模式介绍
1.jdk动态代理
定义一个Mapper接口,目标是获取Select注解上的内容,并执行,执行这一块后面再说
public interface UserMapper {
@Select("select * from user where id = #{id}")
User selectUser(Integer id,String name);
}
测试类
- 关于动态代理的具体实现原理,看设计模式里面的动态代理笔记就知道了,其实只要涉及到jdk动态代理,关注构造这个动态代理时,传入的InvocationHandler的invoke方法就行了
public class UserMapperProxyTest {
public static void main(String[] args) {
UserMapper userMapperProxy =
(UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(),
new Class<?>[]{UserMapper.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Select selectAnno = method.getAnnotation(Select.class);
if (selectAnno != null) {
String[] value = selectAnno.value();
System.out.println(Arrays.toString(value));
}
Arrays.asList(method.getParameters())
.forEach(param-> System.out.println(param.getName()));
// 或者使用spring的读取class文件的方式获取,本来只能读取类的具体参数名,不能读取接口的方法的具体参数名
String[] paramNames = new DefaultParameterNameDiscoverer().getParameterNames(method);
Arrays.asList(paramNames).forEach(System.out::println);
return null;
}
});
userMapperProxy.selectUser(1, "zj");
}
}
执行结果
[select * from user where id = #{id}]
id
name
id
name
2.责任链模式
第一种责任链:每个拦截器都会拦截到
mybatis使用的是下面这种
// 拦截器接口
public interface Interceptor {
public Object intercept(Object target);
}
import com.google.common.collect.Lists;
// 拦截器链
public class InterceptorChain /*implements Interceptor*/ { // 可以考虑下实现这个接口
List<Interceptor> interceptors = Lists.newArrayList();
public Object interceptAll(Object target) {
/*
* 遍历所有的拦截器, 每个拦截器接收上一个拦截器的结果,经过处理后,把结果交给下一个拦截器处理
* 这样每一个拦截器都能对初始的那个对象搞事情
*/
for (Interceptor interceptor : interceptors) {
target = interceptor.intercept(target);
}
return target;
}
public void add(Interceptor interceptor) {
this.interceptors.add(interceptor);
}
}
public class InterceptorTest {
public static void main(String[] args) {
InterceptorChain chain = new InterceptorChain();
chain.add(new Interceptor() {
@Override
public Object intercept(Object target) {
System.out.println("拦截A");
return target;
}
});
chain.add(new Interceptor() {
@Override
public Object intercept(Object target) {
System.out.println("拦截B");
return target;
}
});
chain.add(new Interceptor() {
@Override
public Object intercept(Object target) {
System.out.println("拦截C");
return target;
}
});
chain.interceptAll(new Object());
}
}
/*输出*/
/*
拦截A
拦截B
拦截C
*/
第二种责任链模式:前面的拦截器可以阻断后面的拦截器
跟上面的区别在于,把拦截器链本身传了过去,并且用了一个position这个可以理解为状态变量的东西,每调用一个拦截器,那就记录一下,当前需要被执行的拦截器的索引。当其中一个拦截器断了的时候,即没有调用chain.interceptAll(target,chain);,那这个执行链后面的拦截器就没得玩了。然后逆序调用回去。
public interface Interceptor {
void intercept(Object target,InterceptorChain chain);
}
public class InterceptorChain {
List<Interceptor> interceptors = Lists.newArrayList();
private int position = -1; // 拦截器的位置
public void interceptAll(Object target, InterceptorChain chain) {
position++;
if (position == interceptors.size()) {
return;
}
interceptors.get(position).intercept(target, this);
}
public void add(Interceptor interceptor) {
this.interceptors.add(interceptor);
}
}
public class InterceptorTest {
public static void main(String[] args) {
InterceptorChain chain = new InterceptorChain();
chain.add(new Interceptor() {
@Override
public void intercept(Object target,InterceptorChain chain) {
System.out.println("拦截A");
chain.interceptAll(target,chain);
}
});
chain.add(new Interceptor() {
@Override
public void intercept(Object target,InterceptorChain chain) {
System.out.println("拦截B");
chain.interceptAll(target,chain);
}
});
chain.add(new Interceptor() {
@Override
public void intercept(Object target,InterceptorChain chain) {
System.out.println("拦截C");
chain.interceptAll(target,chain);
}
});
chain.interceptAll(new Object(),chain);
}
}
3.mybatis使用代码
mybatis使用方式,官网给出2种方式,
- 一种是使用xml,
- 一种是java代码的方式
mybatis-confi.xml
<?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">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/mybatis-demo?serverTimezone=UTC"/>
<property name="username" value="zzhua195"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
User类
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
}
在resources下创建mapper/UserMapper.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.zzhua.mapper.UserMapper">
<select id="selectUser" resultType="com.zzhua.jdbc.User">
select * from user where id = #{id}
</select>
</mapper>
UserMapper.java
public interface UserMapper {
User selectUser(Integer id); // 现在上面暂时不要用注解的方式哦
}
概述
-
2种方式不管是xml还是java代码,目的都是先创建Configuration实例,然后把Configuration实例传给DefaultSqlSessionFactory,然后使用DefaultSqlSessionFactory创建SqlSession,使用sqlSession实现sql语句的执行,所以关键就是这个Configuration实例。
-- 之后再看下xml文件是怎么解析的,再挖个坑
xml方式
public class XmlTest {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
// 使用mybatis提供的Resources工具类读取resource,使用的类加载器方式去读的,可以记一下,下次备用
InputStream inputStream = Resources.getResourceAsStream(resource);
// 与下面等价的写法,关流什么的就不纠结了
// SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
Configuration configuration = parser.parse();
DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = (User) session.selectOne("com.zzhua.mapper.UserMapper.selectUser", 1);
System.out.println(user);
}
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUser(1);
System.out.println(user);
}
}
}
java代码方式
public class ConfigurationTest {
public static void main(String[] args) {
PooledDataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver",
"jdbc:mysql://127.0.0.1:3306/mybatis-demo?serverTimezone=UTC",
"zzhua195",
"123456");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
// Environment里面有个数据源,和事务管理器的工厂,这跟xml种标签的配置是一样的
Environment environment = new Environment("development", transactionFactory, dataSource);
// 把environment传给了configuration
Configuration configuration = new Configuration(environment);
// 加载mapper接口,放到Configuration实例的mapperRegistry属性
configuration.addMapper(UserMapper.class);
// SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
try (SqlSession session = sqlSessionFactory.openSession()) {
User user = (User) session.selectOne("com.zzhua.mapper.UserMapper.selectUser", 1);
System.out.println(user);
}
try (SqlSession session = sqlSessionFactory.openSession()) {
com.zzhua.mapper.UserMapper mapper = session.getMapper(com.zzhua.mapper.UserMapper.class);
User user = mapper.selectUser(1);
System.out.println(user);
}
}
}
1.configuration解析出所有的信息,包括数据源和注解和xml等等,并把configuration封装给SqlSessionFactoryBuilder。
2.SqlSessionFactoryBuilder为SqlSession创建Executor执行器,并把Configuration和Executor和autocommit封装给SqlSession
3.从mappedStatement
拦截器
疑问:#{}是怎么变成?的
mybatis调用存储过程:https://blog.csdn.net/dwenxue/article/details/82257944
mybatis缓存文章:https://blog.csdn.net/weixin_34334744/article/details/89045718
一级缓存
一级缓存是SqlSession级别的,默认开启(Configuration的成员变量默认cacheEnabled=true),在使用同一个sqlSession中调用2次,那么只会查询1次,然后把结果放入缓存。如果在查询第一次,使用sqlSession.commit()提交了事务,那么第二次还是会去查询数据库。
二级缓存
mybatis先去查的二级缓存,二级缓存为空才会去查一级缓存(CachingExecutor#query()这个方法里可以看到)
四大对象:
这四个对象都是通过Configuration对应的的newXXX方法创建的(比如newExecutor、newStatementHandler等等),并且在创建完这些对象之后,都会马上接着使用Configuration实例的interceptorChain中的拦截器对这些对象进行拦截。
Executor对象:
- 在openSession的时候创建(DefaultSqlSessionFactory#openSessionFromDataSource),
- 一个sqlsession对应一个Executor对象
StatementHandler对象
- 在执行sql语句的时候创建(SimpleExecutor#doQuery)
- 在SimpleExecutor#prepareStatement方法中,StatementHandler对象使用了Connection连接对象去创建Statement
ParameterHandler对象
- 在创建StatementHandler对象的构造器里,创建了DefaultParameterHandler对象,并且保存到了StatementHandler的parameterHandler属性里。
- StatementHandler创建了statement之后,接着把statement设置给了ParameterHandler对象,由ParameterHandler对象来把参数和statement生成执行的sql(DefaultParameterHandler#setParameters)。
ResultSetHandler对象
-
在创建StatementHandler对象的构造器里,创建了DefaultResultSetHandler对象,并且保存到了StatementHandler的parameterHandler属性里。
-
StatementHandler使用parameterHandler生成执行的sql之后,执行sql(PreparedStatementHandler#query),然后由DefaultResultSetHandler来处理statement执行之后获取到的ResultSet结果集DefaultResultSetHandler#handleResultSets
,MysqlType枚举类里面标识了数据库字段类型和java类的对应关系(结果集里面可以获取到返回的rs里的meta信息,meta信息里有数据库表的字段信息,然后根据字段类型查这个枚举类找到对应的class)。