spring事务管理
来源:互联网 发布:网络会员管理系统免费 编辑:程序博客网 时间:2024/06/12 19:01
参考代码下载github:https://github.com/changwensir/java-ee/tree/master/spring4
•事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
•事务就是一系列的动作,它们被当做一个单独的工作单元.这些动作要么全部完成,要么全部不起作用
•事务的四个关键属性(ACID)
–原子性(atomicity):事务是一个原子操作,由一系列动作组成.事务的原子性确保动作要么全部完成要么完全不起作用.
–一致性(consistency):一旦所有事务动作完成,事务就被提交.数据和资源就处于一种满足业务规则的一致性状态中.
–隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏.
–持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响.通常情况下,事务的结果被写到持久化存储器中.
Spring 中的事务管理
•作为企业级应用程序框架,Spring 在不同的事务管理 API 之上定义了一个抽象层.而应用程序开发人员不必了解底层的事务管理API,就可以使用Spring的事务管理机制.
•Spring 既支持编程式事务管理,也支持声明式的事务管理.
•编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚.在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码.
•声明式事务管理: 大多数情况下比编程式事务管理更好用.它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理.事务管理作为一种横切关注点,可以通过AOP方法模块化.Spring通过 SpringAOP 框架支持声明式事务管理.
Spring 中的事务管理器 •Spring 从不同的事务管理API中抽象了一整套的事务机制.开发人员不必了解底层的事务API,就可以利用这些事务机制.有了这些事务机制,事务管理代码就能独立于特定的事务技术了.
•Spring 的核心事务管理抽象是 Interface PlatFormTransactionManager它为事务管理封装了一组独立于技术的方法.无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的.
1.声明式事务
1).用事务通知声明式地管理事务
•事务管理是一种横切关注点
•为了在 Spring2.x中启用声明式事务管理, 可以通过txSchema中定义的 <tx:advice>元素声明事务通知,为此必须事先将这个Schema定义添加到<beans>根元素中去.
•声明了事务通知后, 就需要将它与切入点关联起来.由于事务通知是在<aop:config>元素外部声明的,所以它无法直接与切入点产生关联.所以必须在<aop:config>元素中声明一个增强器通知与切入点关联起来.
•由于 SpringAOP是基于代理的方法, 所以只能增强公共方法.因此,只有公有方法才能通过Spring AOP 进行事务管理.
建表在最后面,需要注意的是本实例没有用Hibernate或相关的框架,用的是JDBC处理事务,下面这个是基于注解 的
Dao层:
- public interface BookShopDao {
- //根据书号获取书的单价
- int findBookPriceByIsbn(String isbn);
- //更新数的库存. 使书号对应的库存 - 1
- void updateBookStock(String isbn);
- //更新用户的账户余额: 使 username 的 balance - price
- void updateUserAccount(String username, int price);
- }
public interface BookShopDao { //根据书号获取书的单价 int findBookPriceByIsbn(String isbn); //更新数的库存. 使书号对应的库存 - 1 void updateBookStock(String isbn); //更新用户的账户余额: 使 username 的 balance - price void updateUserAccount(String username, int price);}
- @Repository(“bookShopDao”)
- public class BookShopDaoImpl implements BookShopDao {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- public int findBookPriceByIsbn(String isbn) {
- String sql = ”SELECT price FROM book WHERE isbn = ?”;
- return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
- }
- public void updateBookStock(String isbn) {
- //检查书的库存是否足够, 若不够, 则抛出异常
- String sql2 = ”SELECT stock FROM book_stock WHERE isbn = ?”;
- int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
- if(stock == 0){
- throw new BookStockException(“库存不足!”);
- }
- String sql = ”UPDATE book_stock SET stock = stock -1 WHERE isbn = ?”;
- jdbcTemplate.update(sql, isbn);
- }
- public void updateUserAccount(String username, int price) {
- //验证余额是否足够, 若不足, 则抛出异常
- String sql2 = ”SELECT balance FROM account WHERE username = ?”;
- int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
- if(balance < price){
- throw new UserAccountException(“余额不足!”);
- }
- String sql = ”UPDATE account SET balance = balance - ? WHERE username = ?”;
- jdbcTemplate.update(sql, price, username);
- }
- }
@Repository("bookShopDao")public class BookShopDaoImpl implements BookShopDao { @Autowired private JdbcTemplate jdbcTemplate; public int findBookPriceByIsbn(String isbn) { String sql = "SELECT price FROM book WHERE isbn = ?"; return jdbcTemplate.queryForObject(sql, Integer.class, isbn); } public void updateBookStock(String isbn) { //检查书的库存是否足够, 若不够, 则抛出异常 String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?"; int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn); if(stock == 0){ throw new BookStockException("库存不足!"); } String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?"; jdbcTemplate.update(sql, isbn); } public void updateUserAccount(String username, int price) { //验证余额是否足够, 若不足, 则抛出异常 String sql2 = "SELECT balance FROM account WHERE username = ?"; int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username); if(balance < price){ throw new UserAccountException("余额不足!"); } String sql = "UPDATE account SET balance = balance - ? WHERE username = ?"; jdbcTemplate.update(sql, price, username); }}定义两个自定义异常
- public class BookStockException extends RuntimeException{
- private static final long serialVersionUID = 1L;
- public BookStockException() {
- super();
- }
- public BookStockException(String message, Throwable cause,
- boolean enableSuppression, boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- }
- public BookStockException(String message, Throwable cause) {
- super(message, cause);
- }
- public BookStockException(String message) {
- super(message);
- }
- public BookStockException(Throwable cause) {
- super(cause);
- }
- }
public class BookStockException extends RuntimeException{ private static final long serialVersionUID = 1L; public BookStockException() { super(); } public BookStockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public BookStockException(String message, Throwable cause) { super(message, cause); } public BookStockException(String message) { super(message); } public BookStockException(Throwable cause) { super(cause); }}
- public class UserAccountException extends RuntimeException{
- private static final long serialVersionUID = 1L;
- public UserAccountException() {
- super();
- }
- public UserAccountException(String message, Throwable cause,
- boolean enableSuppression, boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- }
- public UserAccountException(String message, Throwable cause) {
- super(message, cause);
- }
- public UserAccountException(String message) {
- super(message);
- }
- public UserAccountException(Throwable cause) {
- super(cause);
- }
- }
public class UserAccountException extends RuntimeException{ private static final long serialVersionUID = 1L; public UserAccountException() { super(); } public UserAccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public UserAccountException(String message, Throwable cause) { super(message, cause); } public UserAccountException(String message) { super(message); } public UserAccountException(Throwable cause) { super(cause); }}Service层
- public interface BookShopService {
- void purchase(String username, String isbn);
- }
public interface BookShopService { void purchase(String username, String isbn);}
- @Service(“bookShopService”)
- public class BookShopServiceImpl implements BookShopService {
- @Autowired
- private BookShopDao bookShopDao;
- //添加事务注解
- //1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
- //如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
- //REQUIRES_NEW: 使用自己的事务, 调用的事务方法的事务被挂起.
- //2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
- //3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
- //属性进行设置. 通常情况下去默认值即可.
- //4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,
- //这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
- //5.使用 timeout 指定强制回滚之前事务可以占用的时间.
- // @Transactional(propagation=Propagation.REQUIRES_NEW,
- // isolation=Isolation.READ_COMMITTED,
- // noRollbackFor={UserAccountException.class})
- @Transactional(propagation=Propagation.REQUIRES_NEW,
- isolation=Isolation.READ_COMMITTED,
- readOnly=false,
- timeout=3)
- public void purchase(String username, String isbn) {
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {}
- //1. 获取书的单价
- int price = bookShopDao.findBookPriceByIsbn(isbn);
- //2. 更新数的库存
- bookShopDao.updateBookStock(isbn);
- //3. 更新用户余额
- bookShopDao.updateUserAccount(username, price);
- }
- }
@Service("bookShopService")public class BookShopServiceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; //添加事务注解 //1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时 //如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务 //REQUIRES_NEW: 使用自己的事务, 调用的事务方法的事务被挂起. //2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED //3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的 //属性进行设置. 通常情况下去默认值即可. //4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据, //这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true //5.使用 timeout 指定强制回滚之前事务可以占用的时间.// @Transactional(propagation=Propagation.REQUIRES_NEW,// isolation=Isolation.READ_COMMITTED,// noRollbackFor={UserAccountException.class}) @Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED, readOnly=false, timeout=3) public void purchase(String username, String isbn) { try { Thread.sleep(5000); } catch (InterruptedException e) {} //1. 获取书的单价 int price = bookShopDao.findBookPriceByIsbn(isbn); //2. 更新数的库存 bookShopDao.updateBookStock(isbn); //3. 更新用户余额 bookShopDao.updateUserAccount(username, price); }}
- public class SpringTransactionTest {
- private ApplicationContext ctx = null;
- private BookShopDao bookShopDao = null;
- private BookShopService bookShopService = null;
- private Cashier cashier = null;
- {
- ctx = new ClassPathXmlApplicationContext(“Spring4_JDBC/applicationContext-tx.xml”);
- bookShopDao = ctx.getBean(BookShopDao.class);
- bookShopService = ctx.getBean(BookShopService.class);
- cashier = ctx.getBean(Cashier.class);
- }
- //
- // @Test
- // public void testTransactionlPropagation(){
- // cashier.checkout(“AA”, Arrays.asList(“1001”, “1002”));
- // }
- //
- @Test
- public void testBookShopService(){
- bookShopService.purchase(”AA”, “1001”);
- }
- @Test
- public void testBookShopDaoUpdateUserAccount(){
- bookShopDao.updateUserAccount(”AA”, 200);
- }
- @Test
- public void testBookShopDaoUpdateBookStock(){
- bookShopDao.updateBookStock(”1001”);
- }
- @Test
- public void testBookShopDaoFindPriceByIsbn() {
- System.out.println(bookShopDao.findBookPriceByIsbn(”1001”));
- }
- }
public class SpringTransactionTest { private ApplicationContext ctx = null; private BookShopDao bookShopDao = null; private BookShopService bookShopService = null; private Cashier cashier = null; { ctx = new ClassPathXmlApplicationContext("Spring4_JDBC/applicationContext-tx.xml"); bookShopDao = ctx.getBean(BookShopDao.class); bookShopService = ctx.getBean(BookShopService.class); cashier = ctx.getBean(Cashier.class); }//// @Test// public void testTransactionlPropagation(){// cashier.checkout("AA", Arrays.asList("1001", "1002"));// }// @Test public void testBookShopService(){ bookShopService.purchase("AA", "1001"); } @Test public void testBookShopDaoUpdateUserAccount(){ bookShopDao.updateUserAccount("AA", 200); } @Test public void testBookShopDaoUpdateBookStock(){ bookShopDao.updateBookStock("1001"); } @Test public void testBookShopDaoFindPriceByIsbn() { System.out.println(bookShopDao.findBookPriceByIsbn("1001")); }}配置文件
- <?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:context=”http://www.springframework.org/schema/context” xmlns:tx=“http://www.springframework.org/schema/tx”
- xmlns:aop=”http://www.springframework.org/schema/aop”
- 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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd”>
- <context:component-scan base-package=“Spring4_JDBC.tx”/>
- <!–导入资源文件–>
- <context:property-placeholder location=”classpath:Spring4_JDBC/db.properties”/>
- <!–配置C3P0数据源–>
- <bean id=”dataSource” class=“com.mchange.v2.c3p0.ComboPooledDataSource”>
- <property name=”user” value=“{jdbc.user}"</span><span>/> </span></span></li><li class=""><span> <property name=<span class="string">"password"</span><span> value=</span><span class="string">"{jdbc.password}”/>
- <property name=”driverClass” value=“{jdbc.driverClass}"</span><span>/> </span></span></li><li class=""><span> <property name=<span class="string">"jdbcUrl"</span><span> value=</span><span class="string">"{jdbc.jdbcUrl}”/>
- <property name=”initialPoolSize” value=“{jdbc.initPoolSize}"</span><span>/> </span></span></li><li class="alt"><span> <property name=<span class="string">"maxPoolSize"</span><span> value=</span><span class="string">"{jdbc.maxPoolSize}”/>
- </bean>
- <!– 配置Spring 的JdbcTemplate–>
- <bean id=”jdbcTemplate” class=“org.springframework.jdbc.core.JdbcTemplate”>
- <property name=”dataSource” ref=“dataSource”/>
- </bean>
- <!– 1. 配置事务管理器,管理JDBC的 –>
- <bean id=”transactionManager” class=“org.springframework.jdbc.datasource.DataSourceTransactionManager”>
- <property name=”dataSource” ref=“dataSource”/>
- </bean>
- <!– 2. 配置事务属性 –>
- <tx:advice id=”txAdvice” transaction-manager=“transactionManager”>
- <tx:attributes>
- <!– 根据方法名指定事务的属性 –>
- <tx:method name=”purchase” propagation=“REQUIRES_NEW”/>
- <tx:method name=”get*” read-only=“true”/>
- <tx:method name=”find*” read-only=“true”/>
- <tx:method name=”*”/>
- </tx:attributes>
- </tx:advice>
- <!– 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 –>
- <aop:config>
- <aop:pointcut expression=”execution(* Spring4_JDBC.tx.*.*(..))”
- id=”txPointCut”/>
- <aop:advisor advice-ref=”txAdvice” pointcut-ref=“txPointCut”/>
- </aop:config>
- </beans>
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" 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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="Spring4_JDBC.tx"/> <!--导入资源文件--> <context:property-placeholder location="classpath:Spring4_JDBC/db.properties"/> <!--配置C3P0数据源--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"/> <property name="password" value="${jdbc.password}"/> <property name="driverClass" value="${jdbc.driverClass}"/> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"/> <property name="initialPoolSize" value="${jdbc.initPoolSize}"/> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"/> </bean> <!-- 配置Spring 的JdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 1. 配置事务管理器,管理JDBC的 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 2. 配置事务属性 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 根据方法名指定事务的属性 --> <tx:method name="purchase" propagation="REQUIRES_NEW"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 --> <aop:config> <aop:pointcut expression="execution(* Spring4_JDBC.tx.*.*(..))" id="txPointCut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config></beans>2).用 @Transactional注解声明式地管理事务
•除了在带有切入点,通知和增强器的Bean配置文件中声明事务外,Spring还允许简单地用 @Transactional注解来标注事务方法.
•为了将方法定义为支持事务处理的,可以为方法添加@Transactional注解. 根据SpringAOP基于代理机制, 只能标注公有方法.
•可以在方法或者类级别上添加@Transactional注解.当把这个注解应用到类上时,这个类中的所有公共方法都会被定义成支持事务处理的.
•在 Bean 配置文件中只需要启用<tx:annotation-driven>元素,并为之指定事务管理器就可以了.
•如果事务处理器的名称是 transactionManager,就可以在<tx:annotation-driven>元素中省略transaction-manager属性.这个元素会自动检测该名称的事务处理器.
3).事务传播属性
•当事务方法被另一个事务方法调用时,必须指定事务应该如何传播.例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行.
•事务的传播行为可以由传播属性指定.Spring定义了 7 种类传播行为.
4).Spring 支持的事务传播行为REQUIRED 传播行为
•当bookService的purchase()方法被另一个事务方法checkout()调用时,它默认会在现有的事务内运行.这个默认的传播行为就是REQUIRED.因此在checkout()方法的开始和终止边界内只有一个事务.这个事务只在checkout()方法结束的时候被提交,结果用户一本书都买不了
•事务传播属性可以在@Transactional注解的propagation属性中定义
REQUIRES_NEW 传播行为
•另一种常见的传播行为是 REQUIRES_NEW.它表示该方法必须启动一个新事务,并在自己的事务内运行.如果有事务在运行,就应该先挂起它.
4).事务其他属性(隔离级别&回滚&只读&过期).
并发事务所导致的问题
•当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题
•并发事务所导致的问题可以分为下面三种类型:
–脏读: 对于两个事物T1,T2, T1 读取了已经被T2更新但还没有被提交的字段.之后,若T2回滚,T1读取的内容就是临时且无效的.
–不可重复读:对于两个事物T1,T2, T1 读取了一个字段,然后T2更新了该字段.之后,T1再次读取同一个字段,值就不同了.
–幻读:对于两个事物T1,T2, T1 从一个表中读取了一个字段,然后T2在该表中插入了一些新的行.之后,如果T1再次读取同一个表,就会多出几行.
事务的隔离级别 •从理论上来说, 事务应该彼此完全隔离,以避免并发事务所导致的问题.然而,那样会对性能产生极大的影响,因为事务必须按顺序运行.
•在实际开发中, 为了提升性能,事务会以较低的隔离级别运行.
•事务的隔离级别可以通过隔离事务属性指定
设置隔离事务属性
•用 @Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别.
•在 Spring2.x 事务通知中, 可以在配置文件中<tx:method>元素中指定隔离级别
设置回滚事务属性 •默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚.而受检查异常不会.
•事务的回滚规则可以通过 @Transactional注解的rollbackFor和noRollbackFor属性来定义.这两个属性被声明为Class[]类型的,因此可以为这两个属性指定多个异常类.
–rollbackFor: 遇到时必须进行回滚
–noRollbackFor:一组异常类,遇到时必须不回滚
设置回滚属性即可以用注解 ,也可以用xml配置文件,同上超时和只读属性
•由于事务可以在行和表上获得锁, 因此长事务会占用资源,并对整体性能产生影响.
•如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化.
•超时事务属性: 事务在强制回滚之前可以保持多久.这样可以防止长期运行的事务占用资源.timeout
•只读事务属性: 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务.readOnly
- CREATE TABLE book(
- isbn VARCHAR(50) PRIMARY KEY,
- book_name VARCHAR(100),
- price INT
- );
- CREATE TABLE book_stock(
- isbn VARCHAR(50) PRIMARY KEY,
- stock INT,
- CHECK(stock > 0)
- );
- CREATE TABLE account(
- username VARCHAR(50) PRIMARY KEY,
- balance INT,
- CHECK(balance > 0)
- );
- INSERT INTO book (isbn, book_name,price) VALUES(“1001”,“Java”,100),(“1002”,“Oracle”,70);
- INSERT INTO account(username,balance) VALUES(“AA”,160);
- INSERT INTO book_stock(isbn,stock)VALUES(“1001”,4),(“1002”,8);
CREATE TABLE book( isbn VARCHAR(50) PRIMARY KEY, book_name VARCHAR(100), price INT);CREATE TABLE book_stock( isbn VARCHAR(50) PRIMARY KEY, stock INT, CHECK(stock > 0) );CREATE TABLE account( username VARCHAR(50) PRIMARY KEY, balance INT, CHECK(balance > 0));INSERT INTO book (isbn, book_name,price) VALUES("1001","Java",100),("1002","Oracle",70);INSERT INTO account(username,balance) VALUES("AA",160);INSERT INTO book_stock(isbn,stock)VALUES("1001",4),("1002",8);
阅读全文
0 0
- 事务管理之Spring事务管理
- Spring事务管理
- spring事务管理
- Spring事务管理
- Spring事务管理
- Spring事务管理
- Spring事务管理
- Spring事务管理
- Spring 事务管理
- Spring事务管理
- spring 事务管理
- spring 事务管理
- spring 事务管理
- spring事务管理
- Spring 事务管理
- Spring事务管理
- spring事务管理
- spring 事务管理
- Linux内核中提供的一些字符串转换函数
- awk查看与统计nginx访问日志
- PAT 1115. Counting Nodes in a BST (30) 搜索树建立 + 各层节点数量判断
- 2017 ACM-ICPC 亚洲区(乌鲁木齐赛区)网络赛【solved:9 / 10】
- Java如何使用redis
- spring事务管理
- MSDNet(Multi-Scale Dense Convolutional Networks)算法笔记
- 二叉树题目
- Python基础知识实例讲解
- Oxford Deep NLP学习笔记1:Word Level Semantics
- Qt 侧边栏
- 我的第一篇博客2017
- 水平集重叠细胞分割
- leetcode 347. Top K Frequent Elements