Mybatis框架深入分析
学习Spring后,您学习了如何使用Bean在IoC容器中管理类。换句话说,现在可以以更方便的方式使用Mybatis框架。可以将SqlSessionFactory、Mapper直接传递给Spring进行管理,并通过注入快速使用
因此,必须学习如何集成Mybatis和Spring。首先,要在以往知识的基础上继续深化学习。
了解数据源
以前,如果需要创建JDBC连接,则必须使用DriverManager.getConnection()创建连接,然后才能使用数据库。
学习Mybatis后,不再使用DriverManager提供连接对象,而是直接使用Mybatis提供的SqlSessionFactory工具类获取相应的SqlSession,并通过会话对象操作数据库。
那么,如何封装JDBC呢?可以考虑Mybatis是否每次都可以调用驱动程序管理器来创建数据库连接。我们可以看到Mybatis的源代码。
Public SQL session open session(布尔型自动commit) {
Return()、(transactionisolationlevel) null、自动commit);
}通过SqlSessionFactory调用openSession方法后,调用了内部专用方法openSessionFromDataSource。此方法的定义如下:
private SQL session opensessionfromdatasource(executor type exec type、transactionisolationlevel level、boolean auto commit)
Transaction tx=null
DefaultSqlSession var8
Try {
//导入当前环境(配置文件映射的对象实体)
environment environment=();
//办公工厂(暂时不提,下一版说明)
transaction factory transaction factory=(环境);
//配置文件:transactionManager type='JDBC'/
//生成事务(默认情况下生成JdbcTransaction,具体取决于配置),其中可以看到使用了environment.getDataSource()方法
Tx=(),级别,自动提交);
//执行器(包括所有数据库操作方法定义)基本上使用执行器操作数据库,需要传递事务对象
Executor executor=(tx,execType);
//封装在SqlSession对象中
Var 8=new defaultsqlsession、executor和auto commit);
} catch (Exception var12) {
(tx);
throw exce(' erroropening session . cause : ' var 12、var 12);
} finally {
ErrorCon()。reset();
}
Return var8:
}也就是说,数据源配置信息存储在Transaction对象中。现在只需知道执行器如何执行SQL语句即可。知道如何创建Connection对象。需要获取数据库的链接信息。那么我们来看看这个数据源是什么。
public interface data source extends common data source、wrapper {
connectiongetconnection()throwsqlexception;
connection getconnection(string username、string password)
Throws SQLException
} javax.sql中定义的接口,包含两种获取连接的方法。因此,可以断定不是通过以前的DriverManager方法获得连接,而是使用DataSource的实现类获得的。因此,这一节的主题被正式介绍。
数据库链接的建立和关闭是一项消耗大量系统资源的任务,是通过驱动程序管理器获得的数据库连接。
数据库连接对象对应于一个物理数据
库连接,每次操作都打开一个物理连接,使用完后立即关闭连接,频繁的打开、关闭连接会持续消耗网络资源,造成整个系统性能的低下。因此,JDBC为我们定义了一个数据源的标准,也就是 DataSource 接口,告诉数据源数据库的连接信息,并将所有的连接全部交给数据源进行集中管理,当需要一个 Connection 对象时,可以向数据源申请,数据源会根据内部机制,合理地分配连接对象给我们。
一般比较常用的 DataSource 实现,都是采用池化技术,就是在一开始就创建好N个连接,这样之后使用就无需再次进行连接,而是直接使用现成的 Connection 对象进行数据库操作。
当然,也可以使用传统的即用即连的方式获取 Connection 对象,Mybatis为我们提供了几个默认的数据源实现,我们之前一直在使用的是官方的默认配置,也就是池化数据源:
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
一共三个选项:
- UNPOOLED 不使用连接池的数据源
- POOLED 使用连接池的数据源
- JNDI 使用JNDI实现的数据源
解读Mybatis数据源实现
那么我们先来看看,不使用池化的数据源实现,它叫做 UnpooledDataSource ,我们来看看源码:
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
private String driver;
private String url;
private String username;
private String password;
private Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
private Integer defaultNetworkTimeout;
首先这个类中定义了很多的成员,包括数据库的连接信息、数据库驱动信息、事务相关信息等。
我们接着来看,它是如何实现 DataSource 中提供的接口的:
public Connection getConnection() throws SQLException {
return , );
}
public Connection getConnection(String username, String password) throws SQLException {
return (username, password);
}
实际上,这两个方法都指向了内部的一个 doGetConnection 方法,那么我们接着来看:
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if != null) {
);
}
if (username != null) {
("user", username);
}
if (password != null) {
("password", password);
}
return (props);
}
首先它将数据库的连接信息也给添加到 Properties 对象中进行存放,并交给下一个 doGetConnection 来处理,套娃就完事了呗,接着来看下一层源码:
private Connection doGetConnection(Properties properties) throws SQLException {
//若未初始化驱动,需要先初始化,内部维护了一个Map来记录初始化信息,这里不多介绍了
();
//传统的获取连接的方式
Connection connection = DriverManager.getConnection, properties);
//对连接进行额外的一些配置
(connection);
return connection;
}
到这里,就返回 Connection 对象了,而此对象正是通过 DriverManager 来创建的,因此,非池化的数据源实现依然使用的是传统的连接创建方式,那我们接着来看池化的数据源实现,它是 PooledDataSource 类:
public class PooledDataSource implements DataSource {
private static final Log log = LogFac);
private final PoolState state = new PoolState(this);
private final UnpooledDataSource dataSource;
protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected int poolMaximumLocalBadConnectionTolerance = 3;
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;
private int expectedConnectionTypeCode;
我们发现,在这里的定义就比非池化的实现复杂得多了,因为它还要考虑并发的问题,并且还要考虑如何合理地存放大量的链接对象,该如何进行合理分配,因此它的玩法非常之高级。
首先注意,它存放了一个UnpooledDataSource,此对象是在构造时就被创建,其实创建Connection还是依靠数据库驱动创建,我们后面慢慢解析,首先我们来看看它是如何实现接口方法的:
public Connection getConnection() throws SQLException {
return (), ()).getProxyConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return (username, password).getProxyConnection();
}
可以看到,它调用了 popConnection() 方法来获取连接对象,然后进行了一个代理,我们可以猜测,有可能整个连接池就是一个类似于栈的集合类型结构实现的。那么我们接着来看看 popConnection 方法:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
//返回的是PooledConnection对象,
PooledConnection conn = null;
long t = Sy();
int localBadConnectionCount = 0;
while(conn == null) {
synchronized) { //加锁,因为有可能很多个线程都需要获取连接对象
PoolState var10000;
//PoolState存了两个List,一个是空闲列表,一个是活跃列表
if (!()) { //有空闲连接时,可以直接分配Connection
conn = (PooledConnection(0); //ArrayList中取第一个元素
if ()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
//如果已经没有多余的连接可以分配,那么就检查一下活跃连接数是否达到最大的分配上限,如果没有,就new一个
} else if .activeConnections.size() < ) {
//注意new了之后并没有立即往List里面塞,只是存了一些基本信息
//我们发现,这里依靠UnpooledDataSource创建了一个Connection对象,并将其封装到PooledConnection中
conn = new PooledConnection(), this);
if ()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
//以上条件都不满足,那么只能从之前的连接中寻找了,看看有没有那种卡住的链接(由于网络问题有可能之前的连接一直被卡住,然而正常情况下早就结束并且可以使用了,所以这里相当于是优化也算是一种捡漏的方式)
} else {
//获取最早创建的连接
PooledConnection oldestActiveConnection = (PooledConnection(0);
long longestCheckoutTime = olde();
//判断是否超过最大的使用时间
if (longestCheckoutTime > (long) {
//超时统计信息(不重要)
++;
var10000 = ;
var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
var10000 = ;
var10000.accumulatedCheckoutTime += longestCheckoutTime;
//从活跃列表中移除此链接信息
.activeConnections.remove(oldestActiveConnection);
//如果开启事务,还需要回滚一下
if (!olde().getAutoCommit()) {
try {
olde().rollback();
} catch (SQLException var15) {
log.debug("Bad connection. Could not roll back");
}
}
//这里就根据之前的连接对象直接new一个新的连接(注意使用的还是之前的Connection对象,只是被重新封装了)
conn = new PooledConnection(olde(), this);
conn.setCreatedTimestam());
conn.setLastUsedTimestam());
//过期
olde();
if ()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
//确实是没得用了,只能卡住了(阻塞)
//然后记录一下有几个线程在等待当前的任务搞完
try {
if (!countedWait) {
++.hadToWaitCount;
countedWait = true;
}
if ()) {
log.debug("Waiting as long as " + + " milliseconds for connection.");
}
long wt = Sy();
.wait((long)); //要是超过等待时间还是没等到,只能放弃
//注意这样的话con就为null了
var10000 = ;
var10000.accumulatedWaitTime += Sy() - wt;
} catch (InterruptedException var16) {
break;
}
}
}
//经过之前的操作,已经成功分配到连接对象的情况下
if (conn != null) {
if ()) { //是否有效
if (!conn.getRealConnection().getAutoCommit()) { //清理之前遗留的事务操作
conn.getRealConnection().rollback();
}
conn.setConnectionTypeCode(), username, password));
conn.setCheckoutTimestamp(Sy());
conn.setLastUsedTimestamp(Sy());
//添加到活跃表中
.activeConnections.add(conn);
//统计信息(不重要)
++.requestCount;
var10000 = ;
var10000.accumulatedRequestTime += Sy() - t;
} else {
//无效的连接,直接抛异常
if ()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
++.badConnectionCount;
++localBadConnectionCount;
conn = null;
if (localBadConnectionCount > + ) {
if ()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
//最后该干嘛干嘛,拿不到连接直接抛异常
if (conn == null) {
if ()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
} else {
return conn;
}
}
经过上面一顿猛如虎的操作之后,我们可以得到以下信息:
如果最后得到了连接对象(有可能是从空闲列表中得到,有可能是直接创建的新的,还有可能是经过回收策略回收得到的)。
那么连接(Connection)对象一定会被放在活跃列表中)
那么肯定有一个疑问,现在我们已经知道获取一个链接会直接进入到活跃列表中,那么,如果一个连接被关闭,又会发生什么事情呢,我们来看看此方法返回之后,会调用 getProxyConnection 来获取一个代理对象,实际上就是 PooledConnection 类:
class PooledConnection implements InvocationHandler {
private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class[]{Connec};
private final int hashCode;
//会记录是来自哪一个数据源创建的的
private final PooledDataSource dataSource;
//连接对象本体
private final Connection realConnection;
//代理的链接对象
private final Connection proxyConnection;
...
它直接代理了构造方法中传入的Connection对象,也是使用JDK的动态代理实现的,那么我们来看一下,它是如何进行代理的:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = me();
//如果调用的是Connection对象的close方法,
if ("close".equals(methodName)) {
//这里并不会真的关闭连接(这也是为什么用代理),而是调用之前数据源的pushConnection方法,将此连接改为为空闲状态
(this);
return null;
} else {
try {
if (!Object.cla())) {
();
//任何操作执行之前都会检查连接是否可用
}
//该干嘛干嘛
return me, args);
} catch (Throwable var6) {
throw Exce(var6);
}
}
}
那么我们最后再来看看 pushConnection 方法:
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized) { //老规矩,先来把锁
//先从活跃列表移除此连接
.activeConnections.remove(conn);
//判断此链接是否可用
if ()) {
PoolState var10000;
//看看闲置列表容量是否已满(容量满了就回不去了)
if .idleConnections.size() < && conn.getConnectionTypeCode() == ) {
var10000 = ;
var10000.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//把唯一有用的Connection对象拿出来,然后重新创建一个PooledConnection
PooledConnection newConn = new PooledConnection(), this);
//放入闲置列表,成功回收
.idleConnections.add(newConn);
newConn.setCreatedTimestam());
newConn.setLastUsedTimestam());
conn.invalidate();
if ()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
.notifyAll();
} else {
var10000 = ;
var10000.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
if ()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {
if ()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
++.badConnectionCount;
}
}
}
这样,我们就已经完全了解了Mybatis的池化数据源的执行流程了。
只不过,无论Connection管理方式如何变换,无论数据源再高级,我们要知道,它都最终都会使用 DriverManager 来创建连接对象,而最终使用的也是 DriverManager 提供的 Connection 对象。
整合Mybatis框架
通过了解数据源,我们已经清楚,Mybatis实际上是在使用自己编写的数据源(数据源有很多,之后我们再聊其他的)默认使用的是池化的数据源,它预先存储了很多的连接对象。
那么我们来看一下,如何将Mybatis与Spring更好的结合呢,比如我们现在希望将SqlSessionFactory交给IoC容器进行管理,而不是我们自己创建工具类来管理(我们之前一直都在使用工具类管理和创建会话)
首先导入依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.13</version>
</dependency>
在mybatis-spring依赖中,为我们提供了SqlSessionTemplate类,它其实就是官方封装的一个工具类,我们可以将其注册为Bean,这样我们随时都可以向IoC容器索要,而不用自己再去编写一个工具类了,我们可以直接在配置类中创建:
@Configuration
@ComponentScan("com.test")
public class TestConfiguration {
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws IOException {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build("myba;));
return new SqlSessionTemplate(factory);
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "- Config 3.0//EN"
";>
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.my;/>
<property name="url" value="jdbc:mysql://localhost:3306/study"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.;/>
</mappers>
</configuration>
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfigura);
SqlSessionTemplate template = con);
TestMapper testMapper = );
Sy());
}
@Mapper
public interface TestMapper {
@Select("select * from student where sid = 1")
Student getStudent();
}
@Data
public class Student {
int sid;
String name;
String sex;
}
最后成功得到Student实体类,证明 SqlSessionTemplate 成功注册为Bean可以使用了。
虽然这样已经很方便了,但是还不够方便,我们依然需要手动去获取Mapper对象,那么能否直接得到对应的Mapper对象呢,我们希望让Spring直接帮助我们管理所有的Mapper,当需要时,可以直接从容器中获取,我们可以直接在配置类上方添加注解:
@MapperScan("com.;)
这样,Spring会自动扫描所有的Mapper,并将其实现注册为Bean,那么我们现在就可以直接通过容器获取了:
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfigura);
TestMapper mapper = con);
Sy());
}
请一定注意,必须存在 SqlSessionTemplate 或是 SqlSessionFactoryBean 的Bean,否则会无法初始化(毕竟要数据库的链接信息)
我们接着来看,如果我们希望直接去除Mybatis的配置文件,那么改怎么去实现呢?
我们可以使用 SqlSessionFactoryBean 类:
@Configuration
@ComponentScan("com.test")
@MapperScan("com.;)
public class TestConfiguration {
@Bean
public DataSource dataSource(){
return new PooledDataSource("com.my;,
"jdbc:mysql://localhost:3306/study", "root", "123456");
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean;
}
}
首先我们需要创建一个数据源的实现类,因为这是数据库最基本的信息,然后再给到 SqlSessionFactoryBean 实例,这样,我们相当于直接在一开始通过IoC容器配置了 SqlSessionFactory ,只需要传入一个 DataSource 的实现即可。
删除配置文件,重新再来运行,同样可以正常使用Mapper。
从这里开始,通过IoC容器,Mybatis已经不再需要使用配置文件了,之后基于Spring的开发将不会再出现Mybatis的配置文件。
1.《【applicationisinterrupted】Mybatis框架深入分析》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《【applicationisinterrupted】Mybatis框架深入分析》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/gl/2513059.html