整合不是什么难事,配置文件照葫芦画瓢总能出来。关键是transaction有问题,总是不能够完成事务。
环境:eclipse for javaee , maven , jdk1.8
依赖的jar包
有一部分不是必须的,比如jackson那个是配合spring返回json串用的,
还比如common-fileupload是用来配合spring上传文件用的。自行进行取舍。
但是这里还是要提一下 ,由于spring transaction(事务)是基于aop实现的,所以也需要aopalliance的jar包。(从别的博文习得的知识)
配置流程
1.常规建立maven项目,并将web-content放置在main/webapp下。
pom.xml和文件目录
文件没有独立建包。看得懂就行。
除pom.xml配置外的,其他依赖的包都是通过maven自动添加。
2.配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<!-- needed for ContextLoaderListener -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 前端控制器The front controller of this Spring Web application, responsible for handling all application requests -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Map all requests to the DispatcherServlet for handling -->
<servlet-mapping>
<servlet-name>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Tip:contextConfigLocation、contextLoaderListener和springDispatcherServlet,在eclipse中通过在空白区域按下,alt+/ ,自动生成,前提是eclipse已经装好了相关的spring插件。
另外,spring配置文件如果不是名为applicationContext.xml的话,就需要在web.xml中使用<context-param>和<listener>指定加载。(别的文章看的,我没有验证过)
3.配置springmvc.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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!--配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置CommonsMultipartResolver,文件的上传 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="utf-8"></property>
</bean>
<!--这是Spring MVC为@Controller分发请求所必需的-->
<mvc:annotation-driven></mvc:annotation-driven>
<!-- 找不到映射,就访问相应的资源 -->
<mvc:default-servlet-handler/>
</beans>
4.配置applicationContext.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:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 指定spring要扫描的包 -->
<context:component-scan base-package="com.maven.spring"></context:component-scan>
<!-- 导入jdbc.properties -->
<context:property-placeholder location="jdbc.properties"/>
<!-- c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialPoolSize" value="10"></property>
<property name="acquireIncrement" value="5"></property>
<property name="minPoolSize" value="5"></property>
<property name="maxPoolSize" value="20"></property>
</bean>
<!-- spring整合 mybatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath*:com/maven/spring/transaction/*.xml"></property>
</bean>
<!-- 自动扫描对象关系映射 ,DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定会话工厂,如果当前上下文中只定义了一个则该属性可省去 -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<!-- 指定要自动扫描接口的基础包,实现接口 -->
<property name="basePackage" value="com.maven.spring.transaction"></property>
</bean>
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 启动@Transactional注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
5.mapper与dao(省略pojo对象)
mapper.java
package com.maven.spring.transaction;
import org.apache.ibatis.annotations.Param;
public interface DepositMapper {
public double getMoney(int id);
public void moneyOut(@Param("id") int id,@Param("money")double money);
public void moneyIn(@Param("id")int id,@Param("money")double money);
}
mybatis 在传入多个参数时使用@Param标记,使得在对应xml文件中能以#{id},#{money}区分,否则默认为#{arg0},#[arg1}或者#{0},#{1},详情:MyBatis传入多个参数的问题
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.maven.spring.transaction.DepositMapper">
<resultMap type="com.maven.spring.transaction.Deposit" id="BaseResultMap">
<result column="user_name" property="userName"/>
</resultMap>
<select id="getMoney" resultType="java.lang.Double">
SELECT money FROM test_deposit WHERE id=#{id}
</select>
<update id="moneyOut" >
UPDATE test_deposit SET money = money-#{money} WHERE id=#{id}
</update>
<update id="moneyIn" >
UPDATE test_deposit SET money = money+#{money} WHERE id=#{id}
</update>
</mapper>
6.daoimpl
package com.maven.spring.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class DepositDao {
@Autowired
private DepositMapper depositMapper=null;
//存钱
public void moneyIn(int id,double money) {
depositMapper.moneyIn(id, money);
System.out.println("money in");
}
//取钱
public void moneyOut(int id,double money) {
double restMoney = depositMapper.getMoney(id);//获得账户余额
if(restMoney > money) {//比较账户余额和所需要取出的量
depositMapper.moneyOut(id, money);
System.out.println("money out"); //控制台输出,测试用
}else {
throw new NotEnoughMoneyException("余额不足!");
}
}
}
这个文件的作用就是在取钱的时候,比较剩余的钱,然后手动抛出异常。(可以根据情况取舍)
因为不抛出异常的话,transaction事务会无法正常运行,或者说回滚。
这个异常是自定义异常类,继承自RuntimeException,重写了构造方法。
7.service层
package com.maven.spring.transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DepositService {
@Autowired
private DepositDao depositDao;
//添加事务注解
@Transactional
public void transferAccountsTransaction() {
depositDao.moneyIn(1, 300);//在账户1加300
depositDao.moneyOut(2, 300);//在账户2减去300
}
}
完成“转账”的功能,故意先将钱加给用户1,在再向用户2扣钱,使异常发生在第二步moneyOut,观察事务是否回滚。
8.测试层(就当成是controller层看吧。。)
package com.maven.spring.transaction;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainTranscation {
DepositService depositService = null;
ApplicationContext ctx=null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
depositService = ctx.getBean(DepositService.class);
}
@Test
public void test() {
System.out.println("do transaction");
depositService.transferAccountsTransaction();
}
}
如果配置正常,在eclipse上会有提示
这个标记和aop的标记是一样的,这更加验证了spring transaction的底层仍然是aop
结果
运行成功,由于数据库用户2(id=2)余额只有200,而我想要取出300元,所以抛出了余额不足的异常。transaction事务回滚,用户1(id =1)的钱没有加上。
附上数据库:
收获:
作为spring初学者:
ApplicationContext ctx=null;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
depositMapper = ctx.getBean(DepositMapper.class);
}
通过这种方式获得的bean,和@autowired自动获得的bean,应该是差不多的,细节还不了解,只知道注解方式在用JUnit单元测试的时候会报空指针异常,且使用注解方式获得bean一定要在目标方法上加上@Service/@Repository/@Component等注解,使配置文件通过component-scan扫描进去才行。
如果稍微在配置的地方将<context:component-scan>扫描包的时候放错放错位置,到springmvc.xml的时候,可能会报java.lang.NoClassDefFoundError: org.springframework.beans.FatalBeanException,在配置中就因为对spring不够了解,出现了很多次这个异常。而在百度上,相当一部分结果认为这个异常是因为jvm内存太小,我这种小程序怎么能和jvm内存扯上关系呢,果断放弃。直到看见一些大牛说,spring<bean>的重复配置可能会导致这个问题。
这篇文章仅代表我的个人的看法,所述观点不一定都对,毕竟对于javaweb要学习的还有很多。希望本文能给予您灵感。
参考文献:
SpringMVC+Spring+mybatis项目从零开始--Spring mybatis mysql配置实现(这个还是挺详细的)
spring+springmvc+mybatis事务失效问题
SpringMVC+MyBatis 事务中 基于注解的声明式事务
浅谈web.xml文件中加载Spring配置文件和SpringMVC配置文件
浅谈配置文件:spring-servlet.xml(spring-mvc.xml) 与 applicationContext.xml(交代了springmvc和spring之间配置文件的关系)
MyBatis传入多个参数的问题 (mybatis基础)