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

yd的佛罗由哈 2016-10-31 04:06:00
下面是我测试的代码,就是用两个线程,分别插入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>
...全文
1481 4 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
目与木鱼 2020-04-28
  • 打赏
  • 举报
回复
每个线程创建自己的sqlsession连接,不要使用同一个
yd的佛罗由哈 2016-10-31
  • 打赏
  • 举报
回复
下面是我测试出来的结果,我把两个线程分别设置了运行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
yd的佛罗由哈 2016-10-31
  • 打赏
  • 举报
回复
spring里面的bean默认都是单例的,那么我上面的service实例自然都是同一个对象,我也输出查看过,确实是。 那么为什么加了synchronized,却不是按顺序执行
yd的佛罗由哈 2016-10-31
  • 打赏
  • 举报
回复
原因我懂了,因为事务之间是并发运行的,所以当然有问题。 那我就用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); /
    }

67,550

社区成员

发帖
与我相关
我的任务
社区描述
J2EE只是Java企业应用。我们需要一个跨J2SE/WEB/EJB的微容器,保护我们的业务核心组件(中间件),以延续它的生命力,而不是依赖J2SE/J2EE版本。
社区管理员
  • Java EE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧