Java学习笔记-Day68 Spring 框架(六)
一、Spring 事务处理
1、Spring 事务处理简介
本地事务(local transaction):使用单一资源管理器,管理本地资源。
全局事务(global transaction):通过事务管理器和多种资源管理器,管理多种不同类型的资源,如JDBC资源和JMS资源。
编程式事务:编程式事务可以是一个代码段,通过编码方式,开启事务、提交事务、回滚事务。
声明式事务:通过 xml配置或注解 实现事务处理。声明式事务边界可以是一个方法或者一个类中的所有方法。Spring AOP和EJB都是声明式事务。
Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
编程式事务侵入到了业务代码里面,但是提供了更加详细的事务处理理。而声明式事务由于基于AOP,所以既能起到事务处理的作用,又可以不影响业务代码的具体实现。
Spring事务处理只能处理运行时异常(RuntimeException)。
事务处理都放在业务层。事务处理操作时使用同一个数据库连接对象。
2、Spring 事务处理相关的类和接口
(1)PlatformTransactionManager接口(抽象模型,顶层接口):描述Spring事务策略。
org.springframework.transaction.PlatformTransactionManager
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
(2)AbstractPlatformTransactionManager抽象类(实现PlatformTransactionManager接口)
org.springframework.transaction.support.AbstractPlatformTransactionManager
@SuppressWarnings("serial")
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
// 部分方法
/**
* 实现commit的部分方法
*/
@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException;
/**
* 实现rollback的部分方法
*/
@Override
public final void rollback(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
processRollback(defStatus, false);
}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
else {
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException;
}
(3)DataSourceTransactionManager类(继承AbstractPlatformTransactionManager抽象类)
org.springframework.jdbc.datasource.DataSourceTransactionManager
@SuppressWarnings("serial")
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
// 部分方法
/**
* 实现commit的部分方法
*/
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
/**
* 实现rollback的部分方法
*/
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
}
(4)TransactionStatus接口:描述事务状态。
org.springframework.transaction.TransactionStatus
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
@Override
void flush();
boolean isCompleted();
}
(5)SavepointManager接口:进行事务回滚的保存点。
org.springframework.transaction.SavepointManager
public interface SavepointManager {
Object createSavepoint() throws TransactionException;
void rollbackToSavepoint(Object savepoint) throws TransactionException;
void releaseSavepoint(Object savepoint) throws TransactionException;
}
3、@Transactional注解
@Transactional:事务处理的注解,可以放在类和方法前,如果是在类前,则是指定该类中的所有方法。通过该注解可以获取同一个数据库连接对象。
@Transactional 注解应该只被应用到 public 方法上。 如果在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,系统也不会报错, 但是这个被注解的方法将不会执行已配置的事务设置。
@Transactional(rollbackFor = Throwable.class)
如果在接口、实现类、方法上都指定了@Transactional注解,则优先级顺序为 方法 > 实现类 > 接口。
二、Spring 事务处理案例
1、DataSource相关的事务处理案例
题目:增加一个员工,同时添加一个用户,如果增加员工和用户同时成功,则增加操作成功。如果增加员工和用户有一个操作失败,则增加操作失败。如果程序出现了异常,操作也是失败。
声明式事务 的操作步骤:
(1)导入与事务管理操作相关的jar包。
(2)获取数据库连接对象。BaseDao类继承JdbcDaoSupport类,在BaseDao类中调用JdbcDaoSupport类的setDataSource方法,给JdbcDaoSupport类的jdbcTemplate赋值。再在BaseDao类中调用JdbcDaoSupport类的getConnection方法,获取数据库连接对象(在不同方法中调用时,返回的都是同一个数据库连接对象)。在JdbcDaoSupport类的getConnection方法中会调用DataSourceUtils类的getConnection方法,而DataSourceUtils类的getConnection方法会对数据库连接对象进行一个简单的注册,保证返回的对象是同一个连接对象。
DataSourceUtils用于管理JDBC,例如数据库连接资源的释放(自动释放)。
package com.etc.aop.dao;
import java.sql.Connection;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Component;
@Component(value="bd")
public class BaseDao extends JdbcDaoSupport{
@Autowired
public void setDataSource(DriverManagerDataSource dataSource) {
super.setDataSource(dataSource);
}
public Connection getConn() throws SQLException {
return super.getConnection();
}
}
(3)员工和用户的实体类。
package com.etc.aop.entity;
import java.util.Date;
public class Staff {
private String sno;
private String name;
private Date birthday;
private String address;
private String tel;
public Staff(String sno, String name, Date birthday, String address, String tel) {
super();
this.sno = sno;
this.name = name;
this.birthday = birthday;
this.address = address;
this.tel = tel;
}
public Staff() {
super();
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
@Override
public String toString() {
return "Staff [sno=" + sno + ", name=" + name + ", birthday=" + birthday + ", address=" + address + ", tel="
+ tel + "]";
}
}
package com.etc.aop.entity;
public class Users {
private String uname;
private String sno;
private String pwd;
private int role;
public Users(String uname, String sno, String pwd, int role) {
super();
this.uname = uname;
this.sno = sno;
this.pwd = pwd;
this.role = role;
}
public Users() {
super();
}
public String getUname() {
return uname;
}
public void setUname(String uname) {
this.uname = uname;
}
public String getSno() {
return sno;
}
public void setSno(String sno) {
this.sno = sno;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public int getRole() {
return role;
}
public void setRole(int role) {
this.role = role;
}
@Override
public String toString() {
return "Users [uname=" + uname + ", sno=" + sno + ", pwd=" + pwd + ", role=" + role + "]";
}
}
(3)Dao接口及其实现类。
package com.etc.aop.dao;
import com.etc.aop.entity.Staff;
public interface StaffDao {
public void addStaff(Staff staff) throws Exception;
}
package com.etc.aop.dao.impl;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.etc.aop.dao.BaseDao;
import com.etc.aop.dao.StaffDao;
import com.etc.aop.entity.Staff;
@Repository(value="sd")
public class StaffDaoImpl implements StaffDao {
@Autowired
private BaseDao bd;
@Override
public void addStaff(Staff staff) throws Exception {
Connection conn = bd.getConn();
System.out.println("staffdao conn:"+conn);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO `tstaff` (`sno`, `name`, `birthday`, `address`, `tel`) VALUES (?,?,?,?,?);");
pstmt.setString(1, staff.getSno());
pstmt.setString(2, staff.getName());
pstmt.setDate(3, new Date(staff.getBirthday().getTime()));
pstmt.setString(4, staff.getAddress());
pstmt.setString(5, staff.getTel());
pstmt.executeUpdate();
pstmt.close();
}
}
package com.etc.aop.dao;
import com.etc.aop.entity.Users;
public interface UsersDao {
public void addUsers(Users user) throws Exception;
}
package com.etc.aop.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.etc.aop.dao.BaseDao;
import com.etc.aop.dao.UsersDao;
import com.etc.aop.entity.Users;
@Repository(value="ud")
public class UsersDaoImpl implements UsersDao {
@Autowired
private BaseDao bd;
@Override
public void addUsers(Users user) throws Exception {
Connection conn = bd.getConn();
System.out.println("userdao conn:"+conn);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO `tuser` (`uname`, `sno`, `pwd`, `role`) VALUES (?,?,?,?);");
pstmt.setString(1, user.getUname());
pstmt.setString(2, user.getSno());
pstmt.setString(3, user.getPwd());
pstmt.setInt(4, user.getRole());
pstmt.executeUpdate();
pstmt.close();
}
}
(4)Service接口及其实现类。
package com.etc.aop.service;
import com.etc.aop.entity.Staff;
import com.etc.aop.entity.Users;
public interface StaffService {
public void addStaffUsers(Staff staff,Users user) throws Exception;
}
package com.etc.aop.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.etc.aop.dao.StaffDao;
import com.etc.aop.dao.UsersDao;
import com.etc.aop.entity.Staff;
import com.etc.aop.entity.Users;
import com.etc.aop.service.StaffService;
@Service(value="ss")
public class StaffServiceImpl implements StaffService {
@Autowired
private StaffDao sd;
@Autowired
private UsersDao ud;
@Override
@Transactional(rollbackFor = Throwable.class)
public void addStaffUsers(Staff staff, Users user) throws Exception{
sd.addStaff(staff);
ud.addUsers(user);
}
}
(5)Spring全局配置文件springaop.xml。
添加命名空间:Namespaces -> tx(事务管理)打勾。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 使用aop有关的注解支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 关于IOC注解支持配置 -->
<context:annotation-config></context:annotation-config>
<context:component-scan base-package="com.etc.aop"></context:component-scan>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="url" value="jdbc:mysql://localhost:3306/empdb?serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
</bean>
<!--事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
三、日志记录
1、日志记录简介
Spring默认有使用日志记录(commoms-logging-1.1.3.jar),可以配置其显示级别。
显示级别 | 显示 |
---|---|
debug | debug、info、warn、error |
info | info、warn、error |
warn | warn、error |
error | error |
2、第三方日志记录 log4j
log4j是Apache的一个开源项目,而log4j2是重新架构的一款日志组件,它抛弃了之前log4j的不足,以及吸取了优秀的logback的设计重新推出的一款新组件。
操作步骤:
(1)加入 log4j-1.2.17.jar 到构建路径下。
(2)添加 log4j.properties 配置文件到src目录下。
log4j.rootCategory=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
log4j.category.org.springframework.beans.factory=debug
# 进行日志记录的路径
log4j.category.com.etc.aop.dao.impl=debug
(3)通过 protected final Log logger = LogFactory.getLog(getClass());
得到一个Logger对象,这个Logger将监视this.getClass()这个运行时类,这个运行时类里面创建了 log.info("infor")
、log.warn("warn")
、log.error("error")
、log.debug("debug")
语句,这些语句就会根据预先定义的Logger级别来输出你的日志。与System.out.print("")一样,但是不同的是Logger可以根据需要按显示级别进行日志输出控制。
package com.etc.aop.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Repository;
import com.etc.aop.dao.BaseDao;
import com.etc.aop.dao.UsersDao;
import com.etc.aop.entity.Users;
@Repository(value="ud")
public class UsersDaoImpl implements UsersDao {
@Autowired
private BaseDao bd;
protected final Log logger = LogFactory.getLog(getClass());
@Override
public void addUsers(Users user) throws Exception {
Connection conn = bd.getConn();
System.out.println("userdao conn:"+conn);
logger.debug("debug");
logger.info("info");
logger.warn("warn");
logger.error("error");
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO `tuser` (`uname`, `sno`, `pwd`, `role`) VALUES (?,?,?,?);");
pstmt.setString(1, user.getUname());
pstmt.setString(2, user.getSno());
pstmt.setString(3, user.getPwd());
pstmt.setInt(4, user.getRole());
pstmt.executeUpdate();
pstmt.close();
}
}