Mybatis框架(03)
1.Mybatis连接池与事务深入
1.1Mybatis的连接池技术
Mybatis有连接池技术,在Mybatis的SqlMapConfig.xml配置文件中,通过来实现Mybatis中连接池的配置。
1.1.1 Mybatis连接池的分类


1.1.2 Mybatis中数据源的配置

1.1.3 Mybatis中DataSource的存取
MyBatis是通过工厂模式来创建数据源DataSource对象的,MyBatis定义了抽象的工厂接口:org.apache.ibatis.datasource.DataSourceFactory,通过其getDataSource()方法返回数据源DataSource。
下面是DataSourceFactory源码,具体如下:
package org.apache.ibatis.datasource;
import java.util.Properties;
import javax.sql.DataSource;
/** * @author Clinton Begin */
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
MyBatis创建了DataSource实例后,会将其放到Configuration对象内的Environment对象中, 供以后使用。 具体分析过程如下:

1.1.4 Mybatis中连接的获取过程分析
当我们需要创建SqlSession对象并需要执行SQL语句时,这时候MyBatis才会去调用dataSource对象来创建java.sql.Connection对象。也就是说,java.sql.Connection对象的创建一直延迟到执行SQL语句的时候。
@Test
public void testSql() throws Exception {
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
SqlSession sqlSession = factory.openSession();
List<User> list = sqlSession.selectList("findUserById",41);
System.out.println(list.size()); }
只有当第4句sqlSession.selectList(“findUserById”),才会触发MyBatis在底层执行下面这个方法来创建java.sql.Connection对象。 如何证明它的加载过程呢? 我们可以通过断点调试,在PooledDataSource中找到如下popConnection()方法,如下所示:

分析源代码,得出PooledDataSource工作原理如下:

下面是连接获取的源代码:

最后我们可以发现,真正连接打开的时间点,只是在我们执行SQL语句时,才会进行。其实这样做我们也可以进一步发现,数据库连接是我们最为宝贵的资源,只有在要用到的时候,才去获取并打开连接,当我们用完了就再立即将数据库连接归还到连接池中。
1.2 Mybatis的事务控制
1.2.1 JDBC中事务的回顾
在JDBC中我们可以通过手动方式将事务的提交改为手动方式,通过setAutoCommit()方法就可以调整。 通过JDK文档,我们找到该方法如下:

那么我们的Mybatis框架因为是对JDBC的封装,所以Mybatis框架的事务控制方式,本身也是用JDBC的setAutoCommit()方法来设置事务提交方式的。
1.2.2 Mybatis中事务提交方式
Mybatis中事务的提交方式,本质上就是调用JDBC的setAutoCommit()来实现事务控制。 我们运行之前所写的代码:
@Test
public void testSaveUser() throws Exception {
User user = new User();
user.setUsername("mybatis user09");
//6.执行操作
int res = userDao.saveUser(user);
System.out.println(res);
System.out.println(user.getId());
}
@Before//在测试方法执行之前执行
public void init() throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建SqlSession工厂对象
factory = builder.build(in);
//4.创建SqlSession对象
session = factory.openSession();
// 5.创建Dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception {
//7.提交事务
session.commit();
//8.释放资源
session.close();
in.close();
}
观察在它在控制台输出的结果:

这是我们的Connection的整个变化过程,通过分析我们能够发现之前的CUD操作过程中,我们都要手动进行事务的提交,原因是setAutoCommit()方法,在执行时它的值被设置为false了,所以我们在CUD操作中,必须通过sqlSession.commit()方法来执行提交操作。
1.2.3 Mybatis自动提交事务的设置
为什么CUD过程中必须使用sqlSession.commit()提交事务?主要原因就是在连接池中取出的连接,都会将调用connection.setAutoCommit(false)方法,这样我们就必须使用sqlSession.commit()方法,相当于使用了JDBC中的connection.commit()方法实现事务提交。
@Before
//在测试方法执行之前执行
public void init() throws Exception {
// 1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// 3.创建SqlSession工厂对象
factory = builder.build(in);
// 4.创建SqlSession对象
session = factory.openSession(true);
// 5.创建Dao的代理对象
userDao = session.getMapper(IUserDao.class);
}
// @After//在测试方法执行完成之后执行
public void destroy() throws Exception {
// 7.释放资源
session.close();
in.close();
}
所对应的DefaultSqlSessionFactory类的源代码:


此时事务就设置为自动提交了,同样可以实现CUD操作时记录的保存。虽然这也是一种方式,但就编程而言,设置为自动提交方式为false再根据情况决定是否进行提交,这种方式更常用。因为我们可以根据业务情况来决定提交是否进行提交。
2.Mybatis的动态SQL语句
有些时候业务逻辑复杂时,SQL是动态变化的。
2.1 <.if>标签
我们根据实体类的不同取值,使用不同的SQL语句来进行查询。比如在id如果不为空时可以根据id查询,如果username不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
2.1.1 持久层Dao接口
/** * 根据用户信息,查询用户列表
* @param user
* * @return
*/
List<User> findByUser(User user);
2.1.2 持久层Dao映射配置
<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</select>
注意:<.if>标签的test属性中写的是对象的属性名,如果是包装类的对象要使用OGNL表达式的写法。 另外要注意where 1=1 的作用~!
2.1.3 测试
@Test public void testFindByUser() {
User u = new User();
u.setUsername("%王%");
u.setAddress("%顺义%");
//6.执行操作
List<User> users = userDao.findByUser(u);
for(User user : users) {
System.out.println(user);
}
}
2.2<.where>标签
为简化上面where 1=1的条件拼装,可以采用标签来简化开发。
2.2.1 持久层Dao映射配置
<!--根据用户信息查询-->
<select id="findByUser"resultType="user"parameterType="user">
<include refid="defaultSql"></include>
<where>
<if test="username!=null and username != '' ">
and username like #{username}
</if>
<if test="address != null">
and address like #{address}
</if>
</where>
</select>
2.3 <.foreach>标签
2.3.1 需求
传入多个id查询用户信息,用下边两个sql实现:
SELECT * FROM USERS WHERE username LIKE '%张%' AND (id =10 OR id =89 OR id=16)
SELECT * FROM USERS WHERE username LIKE '%张%' AND id IN (10,89,16)
这样我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。 这样我们将如何进行参数的传递?
2.3.1.1 在QueryVo中加入一个List集合用于封装参数
/**
* * <p>Title: QueryVo</p>
* * <p>Description: 查询的条件</p>
* * <p>Company: http://www.itheima.com/ </p>
*/
public class QueryVo implements Serializable {
private List<Integer> ids;
public List<Integer> getIds() {
return ids;
}
public void setIds(List<Integer> ids) {
this.ids = ids;
}
}
2.3.2 持久层Dao接口
/**
* 根据id集合查询用户
* * @param vo
* * @return
* */
List<User> findInIds(QueryVo vo);
2.3.3 持久层Dao映射配置

2.3.3.1 编写测试方法

2.4 Mybatis中简化编写的SQL片段
Sql中可将重复的sql提取出来,使用时用include引用即可,最终达到sql重用的目的。
2.4.1 定义代码片段
<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
select * from user
</sql>
2.4.2 引用代码片段
<!-- 配置查询所有操作 -->
<select id="findAll" resultType="user">
<include refid="defaultSql"></include>
</select>
<!-- 根据id查询 -->
<select id="findById" resultType="UsEr" parameterType="int">
<include refid="defaultSql"></include>
where id = #{uid}
</select>
3.Mybatis 多表查询之一对多
本次案例主要以最为简单的用户和账户的模型来分析Mybatis 多表关系。用户为User 表,账户为Account
表。一个用户(User)可以有多个账户(Account)。具体关系如下:


本文深入探讨MyBatis框架的连接池技术和事务控制机制,包括连接池的配置与使用、事务的手动提交与自动提交设置。此外,还介绍了动态SQL语句的应用,如<if>、<where>、<foreach>标签的使用方法及SQL片段的定义与引用技巧。

被折叠的 条评论
为什么被折叠?



