springmvc mybatis 事务管理问题,两个线程并发运行插入数据的问题。

时间:2022-12-20 17:57:46
下面是我测试的代码,就是用两个线程,分别插入100条记录,可是完成后却发现实际少于200条。

我的配置文件已经配置好了事务管理,我也测试过,一旦中间出错,事务确实会回滚,不会修改数据。

可是我用两个线程分别调用这个事务100次来插入数据,却没有200条,因为中间有些数据是重复了。

对于同一个事务的两百次执行,不是按顺序执行吗,显然就是两个线程穿插执行影响到了彼此。

因为正在学习当中,所以有些迷惑。


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-mybatis.xml")
public class TestSellingOrderDao {

@Autowired
private ISellingOrderService sellingOrderService;

@Autowired
private IDealedBookDao dealedBookDao;

@Test
public void sellDaoShouldNotBeNull() {
assertNotNull(sellingOrderDao);

DealedBook dealedBook = new DealedBook();
dealedBook.setBookId(1);
dealedBook.setDealedNum("DealedNum");
dealedBook.setSellingPrice(6);
dealedBook.setRentalPrice(12);
dealedBook.setDistrictId(1);
dealedBook.setUserId(1);
dealedBook.setDatetime(new Date());

for (int i = 1; i <= 1000; i++)
dealedBookDao.insert(dealedBook);

SellingOrder order = new SellingOrder();
order.setUserId(2);
order.setDeliveryMethodId(1);  //
order.setBookId(dealedBook.getBookId());  //
order.setAmount(5);
order.setDatetime(new Date());
order.setPayed(1);
// 取书号:学校编号+日期+待售书的ID
order.setTakingBookNum( "" +
"SYSU" +
new SimpleDateFormat("yyyyMMdd").format(new Date()) +
String.format("%05d", dealedBook.getId())
);

new Thread() {
@Override
public void run() {
for (int i = 1; i <= 100; i++)
sellingOrderService.insertOrder(order); // 执行事务
}
}.start();

for (int i = 1; i <= 100; i++)
sellingOrderService.insertOrder(order); // 执行事务

}
}




package com.databasegroup.service.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.databasegroup.dao.IDealedBookDao;
import com.databasegroup.dao.ISellingOrderDao;
import com.databasegroup.exception.NoEnoughBooksException;
import com.databasegroup.model.DealedBook;
import com.databasegroup.model.SellingOrder;
import com.databasegroup.service.ISellingOrderService;

@Service("sellingOrderService")
public class SellingOrderServiceImpl implements ISellingOrderService {

@Resource
private ISellingOrderDao sellingOrderDao;

@Resource
private IDealedBookDao dealedBookDao;

@Override
public void insertOrder(SellingOrder sellingOrder) {
StringBuilder dealedBookId = new StringBuilder();
int bookId = sellingOrder.getBookId();
int amount  = sellingOrder.getAmount();
List<DealedBook> dealedBooks = 
dealedBookDao.getNoDealedBookByBookIdAndAmount(bookId, amount);
if (dealedBooks.size() != amount) 
{
throw new NoEnoughBooksException("没有足够的书本"); // 错误抛出后,事务会回滚
}
for (DealedBook dealedBook : dealedBooks) {
dealedBook.setSelled(1);
dealedBook.setSelledDatetime(new Date());
dealedBookId.append("" + dealedBook.getId() + '|');
dealedBookDao.update(dealedBook);  // 修改另外表的数据
}
if (dealedBookId.length() > 0) 
dealedBookId = dealedBookId.deleteCharAt(dealedBookId.length()-1);
// 设置dealed_book_id字段的值
sellingOrder.setDealedBookIds(dealedBookId.toString());
sellingOrderDao.insert(sellingOrder); /
}

}


spring-mybatis.xml

<?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:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" 
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans    
                         http://www.springframework.org/schema/beans/spring-beans-4.1.xsd    
                         http://www.springframework.org/schema/context    
                         http://www.springframework.org/schema/context/spring-context-4.1.xsd    
                         http://www.springframework.org/schema/mvc    
                         http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
                         http://www.springframework.org/schema/aop
                         http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
                         http://www.springframework.org/schema/tx
                         http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

<!-- com.databasegroup下的组件(bean),并排除controller -->
<context:component-scan base-package="com.databasegroup">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<mvc:annotation-driven />

<!-- mybatis 配置1:读参数 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties" />
</bean>

<!-- mybatis 配置2:数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${jdbc.initialSize}"></property>
<!-- 连接池最大数量 -->
<property name="maxActive" value="${jdbc.maxActive}"></property>
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${jdbc.maxIdle}"></property>
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${jdbc.minIdle}"></property>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value="${jdbc.maxWait}"></property>
</bean>

<!-- spring和MyBatis整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- 自动扫描映射文件 -->
<property name="mapperLocations" value="classpath:/mapper/*.xml"></property>
<!-- mybatis配置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<!-- 使用别名 -->
<property name="typeAliasesPackage" value="com.databasegroup.model"></property>
</bean>

<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.databasegroup.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- spring declarative transaction management -->
<aop:config>
<aop:pointcut id="allServiceMethods" 
expression="execution(* com.databasegroup.service.impl.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="allServiceMethods" />
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="reduce*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>  


servlet-context.xml

<?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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd">

<!-- 扫描com.databasegroup.fortest.controller下的controller -->
<context:component-scan base-package="com.databasegroup.controller">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>

<!-- 注解驱动 -->
<mvc:annotation-driven />

<!-- 处理静态资源的请求 -->
<mvc:resources location="/WEB-INF/views/css/" mapping="/css/**" />
<mvc:resources location="/WEB-INF/views/js/" mapping="/js/**" />
<mvc:resources location="/images/" mapping="/images/**" />
<mvc:resources location="/WEB-INF/views/admin/" mapping="/admin/**" />

<!-- 不懂标记 -->
<bean
class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />

<!-- 不懂标记 -->
<bean
class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" />

<!-- 定义跳转的文件的前后缀 ,视图模式配置 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>   
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>

<!-- 上传文件 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8" />
<!-- 最大内存大小 -->
<property name="maxInMemorySize" value="10240" />
<!-- 最大文件大小,-1为不限制大小 -->
<property name="maxUploadSize" value="-1" />
</bean>

<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/center"/>
<mvc:mapping path="/books"/>
<mvc:mapping path="/alipay/**"/>
<mvc:mapping path="/buy_rent"/>
<mvc:mapping path="/pay/**"/>
<mvc:mapping path="/rent/**"/>
<mvc:mapping path="/success/**"/>
<bean class="com.databasegroup.interceptor.LoginInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/backend/**"/>
<bean class="com.databasegroup.interceptor.AdminLoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

</beans>

3 个解决方案

#1


原因我懂了,因为事务之间是并发运行的,所以当然有问题。
那我就用synchronized关键来同步方法insertOrder,那么基本上每次调用service的insertOrder,应该都是按顺序来执行吧。
可是我测试了下,加上synchronized关键字,还是结果还是错的

@Override
    public synchronized void insertOrder(SellingOrder sellingOrder) {
        StringBuilder dealedBookId = new StringBuilder();
        int bookId = sellingOrder.getBookId();
        int amount  = sellingOrder.getAmount();
        List<DealedBook> dealedBooks = 
                dealedBookDao.getNoDealedBookByBookIdAndAmount(bookId, amount);
        if (dealedBooks.size() != amount) 
        {
            throw new NoEnoughBooksException("没有足够的书本"); // 错误抛出后,事务会回滚
        }
        for (DealedBook dealedBook : dealedBooks) {
            dealedBook.setSelled(1);
            dealedBook.setSelledDatetime(new Date());
            dealedBookId.append("" + dealedBook.getId() + '|');
            dealedBookDao.update(dealedBook);  // 修改另外表的数据
        }
        if (dealedBookId.length() > 0) 
            dealedBookId = dealedBookId.deleteCharAt(dealedBookId.length()-1);
        // 设置dealed_book_id字段的值
        sellingOrder.setDealedBookIds(dealedBookId.toString());
        sellingOrderDao.insert(sellingOrder); /
    }

#2


spring里面的bean默认都是单例的,那么我上面的service实例自然都是同一个对象,我也输出查看过,确实是。
那么为什么加了synchronized,却不是按顺序执行

#3


下面是我测试出来的结果,我把两个线程分别设置了运行50次,实际上却会有线程少运行,不知道为什么。
加起来起码要100次,还有就是

Thread-2:  2  6|7|8|9|10
main:  3  6|7|8|9|10

这两行右边是插入的数据,实际上应该是要不同才对。我还没找到原因,难道是因为上一个事务(方法)还没来得及提交,下一个事务(方法)就开始执行了,所以就从数据找到了尚未修改的数据?可是这是为什么呢

main:  1  1|2|3|4|5
Thread-2:  2  6|7|8|9|10
main:  3  6|7|8|9|10
Thread-2:  4  11|12|13|14|15
main:  5  11|12|13|14|15
main:  6  16|17|18|19|20
Thread-2:  7  21|22|23|24|25
main:  8  26|27|28|29|30
main:  9  31|32|33|34|35
main:  10  36|37|38|39|40
main:  11  41|42|43|44|45
main:  12  46|47|48|49|50
main:  13  51|52|53|54|55
main:  14  56|57|58|59|60
main:  15  61|62|63|64|65
main:  16  66|67|68|69|70
main:  17  71|72|73|74|75
main:  18  76|77|78|79|80
main:  19  81|82|83|84|85
main:  20  86|87|88|89|90
main:  21  91|92|93|94|95
main:  22  96|97|98|99|100
Thread-2:  23  101|102|103|104|105
main:  24  101|102|103|104|105
main:  25  106|107|108|109|110
main:  26  111|112|113|114|115
main:  27  116|117|118|119|120
main:  28  121|122|123|124|125
Thread-2:  29  126|127|128|129|130
main:  30  126|127|128|129|130
Thread-2:  31  131|132|133|134|135
main:  32  136|137|138|139|140
Thread-2:  33  141|142|143|144|145
main:  34  146|147|148|149|150
main:  35  151|152|153|154|155
main:  36  156|157|158|159|160
main:  37  161|162|163|164|165
main:  38  166|167|168|169|170
main:  39  171|172|173|174|175
main:  40  176|177|178|179|180
Thread-2:  41  181|182|183|184|185
main:  42  186|187|188|189|190
Thread-2:  43  191|192|193|194|195
main:  44  191|192|193|194|195
main:  45  196|197|198|199|200
main:  46  201|202|203|204|205
main:  47  206|207|208|209|210
main:  48  211|212|213|214|215
Thread-2:  49  216|217|218|219|220
main:  50  221|222|223|224|225
Thread-2:  51  226|227|228|229|230
main:  52  231|232|233|234|235
Thread-2:  53  236|237|238|239|240
main:  54  241|242|243|244|245
Thread-2:  55  246|247|248|249|250
main:  56  251|252|253|254|255
Thread-2:  57  256|257|258|259|260
main:  58  261|262|263|264|265
Thread-2:  59  266|267|268|269|270
main:  60  271|272|273|274|275
Thread-2:  61  276|277|278|279|280
main:  62  276|277|278|279|280
main:  63  281|282|283|284|285
main:  64  286|287|288|289|290
main:  65  291|292|293|294|295
main:  66  296|297|298|299|300

#1


原因我懂了,因为事务之间是并发运行的,所以当然有问题。
那我就用synchronized关键来同步方法insertOrder,那么基本上每次调用service的insertOrder,应该都是按顺序来执行吧。
可是我测试了下,加上synchronized关键字,还是结果还是错的

@Override
    public synchronized void insertOrder(SellingOrder sellingOrder) {
        StringBuilder dealedBookId = new StringBuilder();
        int bookId = sellingOrder.getBookId();
        int amount  = sellingOrder.getAmount();
        List<DealedBook> dealedBooks = 
                dealedBookDao.getNoDealedBookByBookIdAndAmount(bookId, amount);
        if (dealedBooks.size() != amount) 
        {
            throw new NoEnoughBooksException("没有足够的书本"); // 错误抛出后,事务会回滚
        }
        for (DealedBook dealedBook : dealedBooks) {
            dealedBook.setSelled(1);
            dealedBook.setSelledDatetime(new Date());
            dealedBookId.append("" + dealedBook.getId() + '|');
            dealedBookDao.update(dealedBook);  // 修改另外表的数据
        }
        if (dealedBookId.length() > 0) 
            dealedBookId = dealedBookId.deleteCharAt(dealedBookId.length()-1);
        // 设置dealed_book_id字段的值
        sellingOrder.setDealedBookIds(dealedBookId.toString());
        sellingOrderDao.insert(sellingOrder); /
    }

#2


spring里面的bean默认都是单例的,那么我上面的service实例自然都是同一个对象,我也输出查看过,确实是。
那么为什么加了synchronized,却不是按顺序执行

#3


下面是我测试出来的结果,我把两个线程分别设置了运行50次,实际上却会有线程少运行,不知道为什么。
加起来起码要100次,还有就是

Thread-2:  2  6|7|8|9|10
main:  3  6|7|8|9|10

这两行右边是插入的数据,实际上应该是要不同才对。我还没找到原因,难道是因为上一个事务(方法)还没来得及提交,下一个事务(方法)就开始执行了,所以就从数据找到了尚未修改的数据?可是这是为什么呢

main:  1  1|2|3|4|5
Thread-2:  2  6|7|8|9|10
main:  3  6|7|8|9|10
Thread-2:  4  11|12|13|14|15
main:  5  11|12|13|14|15
main:  6  16|17|18|19|20
Thread-2:  7  21|22|23|24|25
main:  8  26|27|28|29|30
main:  9  31|32|33|34|35
main:  10  36|37|38|39|40
main:  11  41|42|43|44|45
main:  12  46|47|48|49|50
main:  13  51|52|53|54|55
main:  14  56|57|58|59|60
main:  15  61|62|63|64|65
main:  16  66|67|68|69|70
main:  17  71|72|73|74|75
main:  18  76|77|78|79|80
main:  19  81|82|83|84|85
main:  20  86|87|88|89|90
main:  21  91|92|93|94|95
main:  22  96|97|98|99|100
Thread-2:  23  101|102|103|104|105
main:  24  101|102|103|104|105
main:  25  106|107|108|109|110
main:  26  111|112|113|114|115
main:  27  116|117|118|119|120
main:  28  121|122|123|124|125
Thread-2:  29  126|127|128|129|130
main:  30  126|127|128|129|130
Thread-2:  31  131|132|133|134|135
main:  32  136|137|138|139|140
Thread-2:  33  141|142|143|144|145
main:  34  146|147|148|149|150
main:  35  151|152|153|154|155
main:  36  156|157|158|159|160
main:  37  161|162|163|164|165
main:  38  166|167|168|169|170
main:  39  171|172|173|174|175
main:  40  176|177|178|179|180
Thread-2:  41  181|182|183|184|185
main:  42  186|187|188|189|190
Thread-2:  43  191|192|193|194|195
main:  44  191|192|193|194|195
main:  45  196|197|198|199|200
main:  46  201|202|203|204|205
main:  47  206|207|208|209|210
main:  48  211|212|213|214|215
Thread-2:  49  216|217|218|219|220
main:  50  221|222|223|224|225
Thread-2:  51  226|227|228|229|230
main:  52  231|232|233|234|235
Thread-2:  53  236|237|238|239|240
main:  54  241|242|243|244|245
Thread-2:  55  246|247|248|249|250
main:  56  251|252|253|254|255
Thread-2:  57  256|257|258|259|260
main:  58  261|262|263|264|265
Thread-2:  59  266|267|268|269|270
main:  60  271|272|273|274|275
Thread-2:  61  276|277|278|279|280
main:  62  276|277|278|279|280
main:  63  281|282|283|284|285
main:  64  286|287|288|289|290
main:  65  291|292|293|294|295
main:  66  296|297|298|299|300