引子
基于Spring的xml配置实现账户的CRUD案例
这个例子主要是熟悉一下JdbcTemplate的语法
步骤
- 创建java项目,导入坐标
- 编写Account实体类
- 编写AccountDao接口和实现类
- 编写AccountService接口和实现类
- 编写spring核心配置文件
- 编写测试代码
<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.15</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.13</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.1.5.RELEASE</version></dependency></dependencies>
实体以及dao层
public class Account {private Integer id;private String name;private Double money;}public interface AccountDao {public List<Account> findAll();public Account findById(Integer id);public void save(Account account);public void update(Account account);public void delete(Integer id);}public class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic List<Account> findAll() {String sql = "select * from account";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));}@Overridepublic Account findById(int id) {String sql = "select * from account where id = ?";return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);}@Overridepublic void save(Account account) {String sql = "insert into account where values(null, ?, ?)";jdbcTemplate.update(sql, account.getName(), account.getMoney());}@Overridepublic void update(Account account) {String sql = "update account set money = ? where name = ?";jdbcTemplate.update(sql, account.getMoney(), account.getName());}@Overridepublic void delete(int id) {String sql = "delete from account where id = ?";jdbcTemplate.update(sql, id);}}
服务层
public interface AccountService {List<Account> findAll();Account findById(int id);void save(Account account);void update(Account account);void delete(int id);}@Servicepublic class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Overridepublic List<Account> findAll() {return accountDao.findAll();}@Overridepublic Account findById(int id) {return accountDao.findById(id);}@Overridepublic void save(Account account) {accountDao.save(account);}@Overridepublic void update(Account account) {accountDao.update(account);}@Overridepublic void delete(int id) {accountDao.delete(id);}}
配置文件
<?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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.ning"/><context:property-placeholder location="classpath:jdbc.properties"/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"/></bean></beans>
测试代码
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath:applicationContext.xml"})public class AccountServiceImplTest {@Autowiredprivate AccountService accountService;@Testpublic void testSave() {Account account = new Account();account.setName("lux");account.setMoney(1000d);accountService.save(account);}@Testpublic void testFindAll() {final List<Account> list = accountService.findAll();for (Account account : list) {System.out.println(account);}}@Testpublic void testFindById() {final Account byId = accountService.findById(3);System.out.println(byId);}@Testpublic void testUpdate() {Account account = new Account();account.setName("lux");account.setMoney(2000d);accountService.update(account);}@Testpublic void testDelete() {accountService.delete(4);}}
引子2
实现一个transfer方法
- 创建java项目,导入坐标
- 编写Account实体类
- 编写AccountDao接口和实现类
- 编写AccountService接口和实现类
- 编写spring核心配置文件
- 编写测试代码
<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.15</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.13</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.1.5.RELEASE</version></dependency></dependencies>
entity dao
public class Account {private Integer id;private String name;private Double money;// setter getter....}public interface AccountDao {public void out(String outUser, Double money);public void in(String inUser, Double money);}@Repositorypublic class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void out(String outUser, double money) {String sql = "update account set money = money - ? where name = ?";jdbcTemplate.update(sql, money, outUser);}@Overridepublic void in(String inUser, double money) {String sql = "update account set money = money + ? where name = ?";jdbcTemplate.update(sql, money, inUser);}}
service
public interface AccountService {void transfer(String outUser, String inUser, double money);}@Servicepublic class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Overridepublic void transfer(String outUser, String inUser, double money) {accountDao.out(outUser, money);accountDao.in(inUser, money);}}
核心配置文件
<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan base-package="com.ning"/><context:property-placeholder location="classpath:jdbc.properties"/><aop:aspectj-autoproxy/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"/></bean></beans>
测试代码
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration({"classpath:applicationContext.xml"})public class AccountServiceImplTest {@Autowiredprivate AccountService accountService;@Testpublic void testTransfer() {accountService.transfer("tom", "jerry", 100d);}}
<br />到目前为止其实正常情况下是实现了功能的,唯一的问题在于事务控制。现在的in和out是两个事务。
事务
Spring的事务控制可以分为编程式事务控制和声明式事务控制。
编程式
开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不用。
声明式
开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想。
编程式事务
PlatformTransactionManager接口,是spring的事务管理器,里面提供了我们常用的操作事务的方法。 <br />
* PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类。* Dao层技术是jdbcTemplate或mybatis时:DataSourceTransactionManager* Dao层技术是hibernate时:HibernateTransactionManager* Dao层技术是JPA时:JpaTransactionManager
TransactionDefinition
TransactionDefinition接口提供事务的定义信息(事务隔离级别、事务传播行为等等)
隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读(幻读)。
* ISOLATION_DEFAULT 使用数据库默认级别* ISOLATION_READ_UNCOMMITTED 读未提交* ISOLATION_READ_COMMITTED 读已提交* ISOLATION_REPEATABLE_READ 可重复读* ISOLATION_SERIALIZABLE 串行化
事务传播行为
事务传播行为指的就是当一个业务方法【被】另一个业务方法调用时,应该如何进行事务控制。 
查询一般设计成SUPPORTS 增删改用REQUIRED
事务状态
TransactionStatus 接口提供的是事务具体的运行状态 
可以简单的理解三者的关系:事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事 务状态。
声明式事务
声明式事务控制明确事项:
核心业务代码(目标对象) (切入点是谁?)
事务增强代码(Spring已提供事务管理器))(通知是谁?)
切面配置(切面如何配置?)
例子
使用声明式事务完成案例
- 引入tx命名空间
- 事务管理器通知配置
- 事务管理器AOP配置
- 测试事务控制转账业务代码
<?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:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/s chema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd"><context:component-scan base-package="com.ning"/><context:property-placeholder location="classpath:jdbc.properties"/><aop:aspectj-autoproxy/><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="${jdbc.driverClassName}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"/></bean><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><constructor-arg name="dataSource" ref="dataSource"/></bean><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"/></tx:attributes></tx:advice><aop:config><aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ning.service.impl.AccountServiceImpl.*(..))"/></aop:config></beans>
CRUD常用配置
<tx:attributes><tx:method name="save*" propagation="REQUIRED"/><tx:method name="delete*" propagation="REQUIRED"/><tx:method name="update*" propagation="REQUIRED"/><tx:method name="find*" read-only="true"/><tx:method name="*"/></tx:attributes>
注解方式
在你想加事务的方法前面加这个注解
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false)
注意,注解可以加在类上。
然后修改配置文件,加这个
<tx:annotation-driven/>
纯注解方式
@Configuration@ComponentScan("com.ning")@Import(DataSourceConfig.class)@EnableTransactionManagementpublic class SpringConfig {@Beanpublic JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);}@Beanpublic PlatformTransactionManager getPlatformTransactionManager(@Autowired DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}}@PropertySource("classpath:jdbc.properties")public class DataSourceConfig {@Value("${jdbc.driverClassName}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Beanpublic DataSource getDataSource() {final DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(username);dataSource.setPassword(password);return dataSource;}}
别忘了修改测试类上面的ContextConfiguration注解。
在Servlet中避免多次加载上下文对象
应用上下文对象是通过 new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写 new ClasspathXmlApplicationContext(spring配置文件) , 这样的弊端是配置文件加载多次,应用上下文对象创建多次。
解决思路分析:
在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域 servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。
Spring提供获取应用上下文的工具
上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封 装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供 了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。
所以我们需要做的只有两件事:
- 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
- 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext
<!--全局参数--><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param><!--Spring的监听器--><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
public class AccountServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {this.doPost(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {final WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());final Account account = (Account)context.getBean("account");System.out.println(account);}}
