MVC 中使用 MyBatis
本章目标:
- 掌握mybatis在web应用中怎么用
- mybatis三大对象的作用域和生命周期
- ThreadLocal原理及使用
- 巩固MVC架构模式
- 为学习MyBatis的接口代理机制做准备
主要实现一个银行账户转账的功能。数据库表的设计如下:
# 1. 基本功能实现
较简单,这里不再展开。
# 2. 事务问题
在银行转账等业务中,事务不应该放在 DAO 层中,而是应该放在 Service 层中。
具体做法是在一个线程中,生成一个 SqlSession 并放到 ThreadLocal 中,然后 DAO 层都是从 ThreadLocal 取 SqlSession 并使用,防止多个 DAO 操作创建了多个 SqlSession 而不在一个事务管理中。
由于 Tomcat 会使用线程池等技术,所以在本次请求处理完之后别忘了把 SqlSession 从 ThreadLocal 中移除。
# 3. MyBatis 核心对象的作用域
这部分内容可以在官方文档中找到。
# 3.1 SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
# 3.2 SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
# 3.3 SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}
2
3
# 4. MyBatis 的接口代理机制
我们看一下银行转账代码中 DAO 层的代码。
Dao 类是一些接口,DaoImpl 是对 Dao 接口的实现:
package com.powernode.bank.dao.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtil.openSession();
Account act = (Account)sqlSession.selectOne("account.selectByActno", actno);
return act;
}
@Override
public int update(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.update", act);
return count;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
可以看到,这个dao实现类中的方法代码很固定,基本上就是一行代码,通过 SqlSession 对象调用书写在 mapper.xml 中的 insert、delete、update、select 等方法,这个类中的方法没有任何业务逻辑,那我们能不能动态地生成 Dao 的实现类呢?答案是可以。
自己做的话,可以用 javassist 库或 cglib 库来实现类的动态生成。但好消息是,MyBatis 已经帮我们实现了 Dao 类的动态生成,这也就是他的 接口代理机制。
比如下面是使用 MyBatis 获取 Dao 接口代理类对象的方法:
AccountDao accountDao = (AccountDao)sqlSession.getMapper(AccountDao.class);
这里 accountDao
便是 AccountDao 接口的一个动态生成的实现类,它的实现忠实地使用 SqlSession 来调用 mapper.xml 中的 SQL 语句。
使用以上代码的前提是:AccountMapper.xml 文件中的 namespace 必须和 dao 接口的全限定名称一致,id必须和dao接口中方法名一致。
在 Spring Boot 中使用 MyBatis Starter 来开发基于 MyBatis 的程序时,往往 DAO 层或者 Mapper 层就是只是声明了一个 interface,这个 interface 与 mapper.xml 进行了关联,但我们并没有实现这个 interface,其实这就是 MyBatis 帮我们动态生成了这个类。我们往往编写的 Mapper 接口就会是 MyBatis 为其生成实现类的字节码,同时帮我们 new 了出来,这样我们只需要 @Autowired
就可以用了。