比如:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
settings>
<environments default="mybatisDB">
<environment id="powernodeDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/powernode"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
<environment id="mybatisDB">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
configuration>
使用:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="car">
<insert id="insertCar">
insert into t_car values(null,'8888','法克鱿',30.0,'2000-11-66','捞车')
insert>
mapper>
package com.powernode.mybatis.test;
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.Test;
import java.io.IOException;
public class ConfigurationTest {
@Test
public void testEnviroment () throws IOException {
//获取SqlSessionFactoryFactory对象(采用默认的方式获取)
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//这种方式就是获取的默认环境
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("car.insertCar");
sqlSession.commit();
sqlSession.close();
//这种方式就是通过环境id来使用指定的环境
SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"),"powernodeDB");
SqlSession sqlSession1 = sqlSessionFactory1.openSession();
sqlSession1.insert("car.insertCar");
sqlSession1.commit();
sqlSession1.close();
}
}
conn.setAutoCommit(false);
conn.commit();
jdbcTransaction
managedTransaction
如果type="JDBC"
,那么底层会实例化JdbcTransaction
对象。
如果type="MANAGED"
,那么底层会实例化ManagedTransaction
对象
当事务管理器是:JDBC
采用JDBC的原生事务机制:
开启事务:conn.setAutoCommit(false);
处理业务…
提交事务:conn.commit();
当事务管理器是:MANAGED
交给容器去管理事务,但目前使用的是本地程序,没有容器的支持,当mybatis找不到容器的支持时:没有事务。也就是说只要执行一条DML语句,则提交一次
dataSource配置:
dataSource被称为数据源。
dataSource作用是什么?
数据源实际上是一套规范。JDK中有这套规范: javax.sql.DataSource
(这个数据源的规范,这套接口实际上是JDK规定的。)
我们自己也可以编写数据源组件,只要实现javax.sql.DataSource
接口就行了。实现接口当中所有的方法。这样就有了自己的数据源。
比如可以写一个属于自己的数据库连接池(数据库连接池是提供连接对象的,所以数据库连接池就是一个数据源)。
常见的数据源组件有哪些呢【常见的数据库连接池有哪些呢】?
type属性 用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象:
type属性 有三个值:必须是三选一。
type=“[ UNPOOLED
| POOLED
| JNDI
]”
JNDI是一套规范。谁实现了这套规范呢?大部分的web容器都实现了JNDI规范:
例如: Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。
JNDI是: java命名目录接口。Tomcat服务器实现了这个规范。
不同配置下的属性不同,通过参考官方手册进行编辑配置
提醒:正常使用连接池的话,池中有很多参数是需要设置的。设置好参数,可以让连接池发挥的更好。事半功倍的效果。
具体连接池当中的参数如何配置呢?需要反复的根据当前业务情况进行测试。
<dataSource type="JNDI">
<property name="initial_context" value="..."/>
<property name="data_soucre" value="..."/>
</dataSource>
这里面不同的配置下面的这个属性都不一样,具体写什么不是个人说的算,要参考官方手册
POOLED:
这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例所必需的初始化和认证时间。这种处理方式很流行,能使并发Web应用快速响应请求
属性 | 作用 |
---|---|
poolMaximumActiveConnections | 最大的活动的连接数量。默认值10 |
poolMaximumIdleConnections | 最大的空闲连接数量。默认值5 |
poolMaximumCheckoutTime | 强行回归池的时间。默认值20秒 |
poolTimeToWait | 当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的) |
poolPingQuery | 发送到数据库的侦测查询,用来检测连接是否工作并准备结束请求。默认是“NO PING QUERY SET”,这会导致多数据库驱动出错时返回恰当的错误信息 |
poolPingEnabled | 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的sql语句(最好是一个速度非常快的SQL语句),默认值:false |
poolMaximumLocalBadConnectionTolerance | 这是一个关于坏连接容忍度的底层设置,作用于每一个参数缓存池获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaximumIdleConnections与poolMaximumLocalBadConnectionTolerance之和默认值:3(新增于3.4.5) |
poolPingConncetionsNotUsedFor | 配置poolPingQuery的频率。可以被设置为和数据库连接超过时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测——当然仅当poolPingEnabled为true时适用) |
在POOLED的数据源下开启:
@Test
public void testDataSource() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// sqlSessionFactory对象:一个数据一个,不要频繁地创建改对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
//通过sqlSessionFactory对象可以开启多个会话
//会话1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
sqlSession1.insert("car.insertCar");
sqlSession1.commit();
sqlSession1.close();//因为要测试连接池的效果,关闭是需要的。别忘了,要不然测不出来
//会话2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
sqlSession2.insert("car.insertCar");
sqlSession2.commit();
sqlSession2.close();
}
区别:使用连接池的话就是同一个连接,sqlSession每次关闭的时候都会把连接返回到池里面,下一次就会重新调用
UNPOOLED:
这个数据源的实现会没有请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。
属性 | 作用 |
---|---|
driver | 这是JDBC驱动的java类全限定名(并不是JDBC驱动钟可能包含的数据源类) |
url | 这是数据库的JDBC URL地址 |
name | 登录数据库的用户名 |
password | 登录数据库的密码 |
defaultTransactionIsolationLevel | 默认的连接事务隔离级别 |
defaultNetworkTimeout | 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看java.sqlConnection#NetworkTimeout() 的API文档以获取更多信息。作为可选项,你也可以传递属性给数据库驱动。值需在属性名加上"driver. "前缀即可,例如:driver.encoding=UTF8 这将通过DriverManager.getConncetion(url,driverProperties) 方法传递值为UTF8的encoding属性给数据库驱动 |
在UNPOOLED的数据源下开启:
@Test
public void testDataSource() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// sqlSessionFactory对象:一个数据一个,不要频繁地创建改对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
//通过sqlSessionFactory对象可以开启多个会话
//会话1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
sqlSession1.insert("car.insertCar");
sqlSession1.commit();
sqlSession1.close();//因为要测试连接池的效果,关闭是需要的。别忘了,要不然测不出来
//会话2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
sqlSession2.insert("car.insertCar");
sqlSession2.commit();
sqlSession2.close();
}
区别:没有用连接池的话,每次就会新建一个对象,但是连接数据库是一个进程、java虚拟机又是一个进程,每一次创建那么效率就低
正常使用连接池的话,池中有很多参数是需要设置的。设置号参数,可以让连接池发挥的更好,事半功倍的效果
具体连接池当中的参数如何配置呢?需要反复根据当前的业务情况进行测试。比如:一个网站的并发量在晚上的时候是多少多少人,但是在某个系统的时候又是不一样的,不一样的话连接池就要有不同的配置
@Test
public void testDataSource() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// sqlSessionFactory对象:一个数据一个,不要频繁地创建改对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
//通过sqlSessionFactory对象可以开启多个会话
//会话1
// SqlSession sqlSession1 = sqlSessionFactory.openSession();
// sqlSession1.insert("car.insertCar");
// sqlSession1.commit();
// sqlSession1.close();//因为要测试连接池的效果,关闭是需要的。别忘了,要不然测不出来
//
// //会话2
// SqlSession sqlSession2 = sqlSessionFactory.openSession();
// sqlSession2.insert("car.insertCar");
// sqlSession2.commit();
// sqlSession2.close();
for (int i = 0; i < 4; i++) {
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.insert("car.insertCar");
//不要关闭
}
}
属性 | 作用 |
---|---|
poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限 | 最大的活动的连接数量。默认值10 |
poolMaximumIdleConnections | 最大的空闲连接数量。默认值5 |
poolMaximumCheckoutTime | 强行回归池的时间。默认值20秒 |
poolTimeToWait | 当无法获取到空闲连接时,每隔20秒打印一次日志,避免因代码配置有误,导致傻等。(时长是可以配置的) |
当然,还有其他属性。对于连接池来说,以上几个属性比较重要。
最大的活动的连接数量就是连接池连接数量的上限。默认值10,如果有10个请求正在使用这10个连接,第11个请求只能等待空闲连接。
最大的空闲连接数量。默认值5,如何已经有了5个空闲连接,当第6个连接要空闲下来的时候,连接池会选择关闭该连接对象。来减少数据库的开销。
需要根据系统的并发情况,来合理调整连接池最大连接数以及最多空闲数量。充分发挥数据库连接池的性能。【可以根据实际情况进行测试,然后调整一个合理的数量。】
mybatis提供了更加灵活的配置,连接数据库的信息可以单独写到一个属性资源文件中,假设在类的根路径下创建jdbc.properties文件,配置如下:
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/powernode
jdbc.username = root
jdbc.password = root
在mybatis核心配置文件中引入并使用:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<environments default="powernodeDB">
<environment id="powernodeDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="poolMaximumActiveConnections" value="10"/>
dataSource>
environment>
<environment id="mybatisDB">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
configuration>
测试成功:
properties两个属性:
file:///d:/jdbc.properties
。注意是三个斜杠。但是这种方式不建议,因为路径定死了注意:如果不知道mybatis-config.xml文件中标签的编写顺序的话,可以有两种方式知道它的顺序:
- 第一种方式:查看dtd约束文件。
- 第二种方式:通过idea的报错提示信息。【一般采用这种方式】
mapper标签用来指定SQL映射文件的路径,包含多种指定方式,这里先主要看其中两种:
<mappers>
<mapper resource="CarMapper.xml"/>
mappers>
如果是这样写的话,必须保证类的根下有CarMapper.xml文件。
如果类的根路径下有一个包叫做test,CarMapper.xml如果放在test包下的话,这个配置应该是这样写:
<mappers>
<mapper resource="test/CarMapper.xml"/>
mappers>
<mappers>
<mapper url="file:///d:/CarMapper.xml"/>
mappers>
目标:
- 掌握mybatis在web应用中怎么用
- mybatis三大对象(sqlSession、factory、builder)的作用域和生命周期
- ThreadLocal原理及使用
- 巩固MVC架构模式
- 为学习MyBatis的接口代理机制做准备
实现功能:
使用技术:
WEB应用的名称:
web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="true">
web-app>
pop
<dependencies>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.5.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.2.11version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
dependency>
dependencies>
index.xml
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>银行账号转账title>
head>
<body>
<form action="/bank/transfer" method="post">
转出账号: <input type="text" name="fromActno"><br>
转入账号:<input type="text" name="toActno"><br>
转账金额:<input type="text" name="money"><br>
<input type="submit" value="转账">
form>
body>
html>
创建pojo包、service包、dao包、web包、utils包
utils包
package com.powernode.bank.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;
//mybatis工具类
public class SqlSessionUtil {
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession openSession(){
return sqlSessionFactory.openSession();
}
}
写好的工具类,方便在dao层获取sqlsession对象。
pojo包
package com.powernode.bank.pojo;
/**
* @description: 账户类,封装账户数据
* @author 小黑子
* @date 2023/9/23 15:51
* @version 1.0
*/
public class Account {
private Long id;
private String actno;
private Double balance;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public Double getBalance() {
return balance;
}
public void setBalance(Double balance) {
this.balance = balance;
}
public Account(Long id, String actno, Double balance) {
this.id = id;
this.actno = actno;
this.balance = balance;
}
public Account() {
}
}
pojo是封装类,账号的类型里面有的相关数据,一方面能在java中进行数据操作,另一方面对接MySQL的账户信息,方便CRUD。
service包
package com.powernode.bank.service;
/**
* 业务类当中的业务方法的名字在起名的时候,最好见名知意,能够体系出具体的业务是做什么的
* @description: 账户业务类
* @author 小黑子
* @date 2023/9/23 16:40
* @version 1.0
*/
public interface AccountService {
/**
* fromActno 转出账户
* toActno 转入账号
* money 转账金额
*/
void transfer(String fromActno,String toActno, double money);
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
//1、判断转出账户的余额是否充足
Account fromAct = accountDao.selectByActno(fromActno);
if (fromAct.getBalance() < money){
//2、如果转出账户余额不足,提示用户
throw new MoneyNotEnoughException("对不起,您的余额不足");
}
//3、如果转出账户余额充足,更新转出账户余额(update)
//先更新内存中java对象account的余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
int count = accountDao.updateByActno(fromAct);
//4、更新转入账户余额(update)
count += accountDao.updateByActno(toAct);
if (count != 2){
throw new TransferException("转账异常,未知原因");
}
}
}
service层是业务逻辑层,主要是对数据进行逻辑的处理,页面的改变等。这里的逻辑主要就是对余额先进行能否转移的判断,其次是对账户余额的转移改变,最后就是对不同错误的出现返回不同的错误类型
dao包
package com.powernode.bank.dao;
import com.powernode.bank.pojo.Account;
/**
* @description: 账户的DAO对象.负责t_cat表中数据的CRUD
* DAO对象中的任何一个方法和业务不挂钩.没有任何业务逻辑在里面
* DAO中的方法就是做crud的.所以方法名大部分是:insertXXX deleteXXX updateXXX
* @author 小黑子
* @date 2023/9/23 17:42
* @version 1.0
*/
public interface AccountDao {
/*
* @description: 根据账号查询账户信息
* @param actno 账号
* @return 账户信息
*/
Account selectByActno(String actno);
/*
* @description: 更新账户信息
* @param act 被更新的账户对象
* @return 1表示更新成功 其他值表示失败
*/
int updateByActno(Account act);
}
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 account = (Account) sqlSession.selectOne("account.selectByActno", actno);
sqlSession.close();
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.updateActno",act);
sqlSession.commit();
sqlSession.close();
return count;
}
}
分析dao中至少要提供几个方法,才能完成转账:
转账前需要查询余额是否充足:selectByActno
转账时要更新账户:update
dao层是三层架构的最底层,是数据访问层,主要是连接MySQL,并做数据的CRUD,不包含任何的逻辑
web包
package com.powernode.bank.web;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
//为了让这个对象在其他方法中也可以用
private AccountService accountService = new AccountServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException{
//获取表单数据
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Double.parseDouble(request.getParameter("money"));
try {
//调用servlet的转账方法完成转账。(调业务层)
accountService.transfer(fromActno,toActno,money);
//程序能够走到这里,表示转账一定成功了
//调用view完成展示结果
response.sendRedirect(request.getContextPath() + "/success.html");
} catch (MoneyNotEnoughException e) {
response.sendRedirect(request.getContextPath() + "/error1.html");
} catch (TransferException e) {
response.sendRedirect(request.getContextPath() + "/error2.html");
}
}
}
展示层,获取表单数据以及针对不同情况错误的出现展示不同的页面信息。
exceptions报错包
package com.powernode.bank.exceptions;
/*
* @description: 余额不足异常
*/
public class MoneyNotEnoughException extends Exception{
public MoneyNotEnoughException(){}
public MoneyNotEnoughException(String msg){
super(msg);
}
}
TransferException
package com.powernode.bank.exceptions;
public class TransferException extends Exception{
public TransferException(){}
public TransferException(String msg){
}
}
提示页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>转账报告title>
head>
<body>
<h1>余额不足!!h1>
body>
html>
为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到ThreadLocal当中。
package com.powernode.bank.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;
//mybatis工具类
public class SqlSessionUtil {
private SqlSessionUtil(){}
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
//全局的,事务器级别的,一个服务器当中定义一个即可
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
/*
* @description: 获取会话对象
* @return:会话对象
*/
public static SqlSession openSession(){
SqlSession sqlSession = local.get();
if(sqlSession == null){
sqlSession = sqlSessionFactory.openSession();
// 奖sqlSession对象绑定到当前线程上
local.set(sqlSession);
}
return sqlSession;
}
/*
* @description: 关闭sqlSession对象(从当前线程中异常SqlSession对象)
* @version 1.0
*/
public static void close(SqlSession sqlSession){
if (sqlSession == null) {
sqlSession.close();
// 注意移除SqlSession对象和当前线程的绑定关系
// 因为Tomcat服务器支持线程池。也就是说:用过的线程对象t1,可能下一次还会使用这个t1线程
local.remove();
}
}
}
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 account = (Account) sqlSession.selectOne("account.selectByActno",actno);
return account;
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtil.openSession();
int count = sqlSession.update("account.updateActno", act);
return count;
}
}
package com.powernode.bank.service.impl;
import com.powernode.bank.dao.AccountDao;
import com.powernode.bank.dao.impl.AccountDaoImpl;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.pojo.Account;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
//添加事务控制代码
SqlSession sqlSession = SqlSessionUtil.openSession();
//1.判断转出账号的余额是否充足(select)
Account fromAct = accountDao.selectByActno(fromActno);
if(fromAct.getBalance() < money){
//2.如果转出账户余额不足,提示用户
throw new MoneyNotEnoughException("余额不足!");
}
//3. 如果转出账号余额充足,更新转出账户余额(update)
//先更新内存中java对象account的余额
Account toAct = accountDao.selectByActno(toActno);
fromAct.setBalance(fromAct.getBalance() - money);
toAct.setBalance(toAct.getBalance() + money);
int count = accountDao.updateByActno(fromAct);
//模拟异常
String s = null;
s.toString();
//4.更新转入账户余额(update)
count += accountDao.updateByActno(toAct);
if(count != 2){
throw new TransferException("转账异常!!,未知原因");
}
//提交事务
sqlSession.commit();
//关闭事务
SqlSessionUtil.close(sqlSession);
}
}
package com.powernode.bank.web;
import com.powernode.bank.exceptions.MoneyNotEnoughException;
import com.powernode.bank.exceptions.TransferException;
import com.powernode.bank.service.AccountService;
import com.powernode.bank.service.impl.AccountServiceImpl;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
//为了让这个对象在其他方法中也可以用
private AccountService accountService = new AccountServiceImpl();
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException{
//获取表单数据
String fromActno = request.getParameter("fromActno");
String toActno = request.getParameter("toActno");
double money = Double.parseDouble(request.getParameter("money"));
try {
//调用servlet的转账方法完成转账。(调业务层)
accountService.transfer(fromActno,toActno,money);
//程序能够走到这里,表示转账一定成功了
//调用view完成展示结果
response.sendRedirect(request.getContextPath() + "/success.html");
} catch (MoneyNotEnoughException e) {
response.sendRedirect(request.getContextPath() + "/error1.html");
} catch (TransferException e) {
response.sendRedirect(request.getContextPath() + "/error2.html");
}catch (Exception e){
response.sendRedirect(request.getContextPath() + "/error2.html");
}
}
}
SqlSessionFactoryBuilder
SqlSessionFactory
,就不再需要它了。 因此 SqlSessionFactoryBuilder
实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder
来创建多个 SqlSessionFactory
实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。SqlSessionFactory
SqlSessionFactory
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory
的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory
被视为一种代码“坏习惯”。因此 SqlSessionFactory
的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。SqlSession
SqlSession
实例。SqlSession
的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession
实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession
实例的引用放在任何类型的托管作用域中,比如 Servlet
框架中的 HttpSession
。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession
放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession
,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally
块中。下面的示例就是一个确保
SqlSession 关闭的标准模式:
try (SqlSession session = sqlSessionFactory.openSession()) {
// 你的应用逻辑代码
}