引子

基于Spring的xml配置实现账户的CRUD案例
这个例子主要是熟悉一下JdbcTemplate的语法

步骤

  1. 创建java项目,导入坐标
  2. 编写Account实体类
  3. 编写AccountDao接口和实现类
  4. 编写AccountService接口和实现类
  5. 编写spring核心配置文件
  6. 编写测试代码
  1. <dependencies>
  2. <dependency>
  3. <groupId>mysql</groupId>
  4. <artifactId>mysql-connector-java</artifactId>
  5. <version>5.1.47</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.alibaba</groupId>
  9. <artifactId>druid</artifactId>
  10. <version>1.1.15</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-context</artifactId>
  15. <version>5.1.5.RELEASE</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.aspectj</groupId>
  19. <artifactId>aspectjweaver</artifactId>
  20. <version>1.8.13</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework</groupId>
  24. <artifactId>spring-jdbc</artifactId>
  25. <version>5.1.5.RELEASE</version>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework</groupId>
  29. <artifactId>spring-tx</artifactId>
  30. <version>5.1.5.RELEASE</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>junit</groupId>
  34. <artifactId>junit</artifactId>
  35. <version>4.12</version>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.springframework</groupId>
  39. <artifactId>spring-test</artifactId>
  40. <version>5.1.5.RELEASE</version>
  41. </dependency>
  42. </dependencies>

实体以及dao层

  1. public class Account {
  2. private Integer id;
  3. private String name;
  4. private Double money;
  5. }
  6. public interface AccountDao {
  7. public List<Account> findAll();
  8. public Account findById(Integer id);
  9. public void save(Account account);
  10. public void update(Account account);
  11. public void delete(Integer id);
  12. }
  13. public class AccountDaoImpl implements AccountDao {
  14. @Autowired
  15. private JdbcTemplate jdbcTemplate;
  16. @Override
  17. public List<Account> findAll() {
  18. String sql = "select * from account";
  19. return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
  20. }
  21. @Override
  22. public Account findById(int id) {
  23. String sql = "select * from account where id = ?";
  24. return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
  25. }
  26. @Override
  27. public void save(Account account) {
  28. String sql = "insert into account where values(null, ?, ?)";
  29. jdbcTemplate.update(sql, account.getName(), account.getMoney());
  30. }
  31. @Override
  32. public void update(Account account) {
  33. String sql = "update account set money = ? where name = ?";
  34. jdbcTemplate.update(sql, account.getMoney(), account.getName());
  35. }
  36. @Override
  37. public void delete(int id) {
  38. String sql = "delete from account where id = ?";
  39. jdbcTemplate.update(sql, id);
  40. }
  41. }

服务层

  1. public interface AccountService {
  2. List<Account> findAll();
  3. Account findById(int id);
  4. void save(Account account);
  5. void update(Account account);
  6. void delete(int id);
  7. }
  8. @Service
  9. public class AccountServiceImpl implements AccountService {
  10. @Autowired
  11. private AccountDao accountDao;
  12. @Override
  13. public List<Account> findAll() {
  14. return accountDao.findAll();
  15. }
  16. @Override
  17. public Account findById(int id) {
  18. return accountDao.findById(id);
  19. }
  20. @Override
  21. public void save(Account account) {
  22. accountDao.save(account);
  23. }
  24. @Override
  25. public void update(Account account) {
  26. accountDao.update(account);
  27. }
  28. @Override
  29. public void delete(int id) {
  30. accountDao.delete(id);
  31. }
  32. }

配置文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/context
  9. http://www.springframework.org/schema/context/spring-context.xsd">
  10. <context:component-scan base-package="com.ning"/>
  11. <context:property-placeholder location="classpath:jdbc.properties"/>
  12. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  13. <property name="driverClassName" value="${jdbc.driverClassName}"/>
  14. <property name="url" value="${jdbc.url}"/>
  15. <property name="username" value="${jdbc.username}"/>
  16. <property name="password" value="${jdbc.password}"/>
  17. </bean>
  18. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  19. <constructor-arg name="dataSource" ref="dataSource"/>
  20. </bean>
  21. </beans>

测试代码

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration({"classpath:applicationContext.xml"})
  3. public class AccountServiceImplTest {
  4. @Autowired
  5. private AccountService accountService;
  6. @Test
  7. public void testSave() {
  8. Account account = new Account();
  9. account.setName("lux");
  10. account.setMoney(1000d);
  11. accountService.save(account);
  12. }
  13. @Test
  14. public void testFindAll() {
  15. final List<Account> list = accountService.findAll();
  16. for (Account account : list) {
  17. System.out.println(account);
  18. }
  19. }
  20. @Test
  21. public void testFindById() {
  22. final Account byId = accountService.findById(3);
  23. System.out.println(byId);
  24. }
  25. @Test
  26. public void testUpdate() {
  27. Account account = new Account();
  28. account.setName("lux");
  29. account.setMoney(2000d);
  30. accountService.update(account);
  31. }
  32. @Test
  33. public void testDelete() {
  34. accountService.delete(4);
  35. }
  36. }

引子2

实现一个transfer方法

  1. 创建java项目,导入坐标
  2. 编写Account实体类
  3. 编写AccountDao接口和实现类
  4. 编写AccountService接口和实现类
  5. 编写spring核心配置文件
  6. 编写测试代码
  1. <dependencies>
  2. <dependency>
  3. <groupId>mysql</groupId>
  4. <artifactId>mysql-connector-java</artifactId>
  5. <version>5.1.47</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.alibaba</groupId>
  9. <artifactId>druid</artifactId>
  10. <version>1.1.15</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.springframework</groupId>
  14. <artifactId>spring-context</artifactId>
  15. <version>5.1.5.RELEASE</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.aspectj</groupId>
  19. <artifactId>aspectjweaver</artifactId>
  20. <version>1.8.13</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>org.springframework</groupId>
  24. <artifactId>spring-jdbc</artifactId>
  25. <version>5.1.5.RELEASE</version>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework</groupId>
  29. <artifactId>spring-tx</artifactId>
  30. <version>5.1.5.RELEASE</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>junit</groupId>
  34. <artifactId>junit</artifactId>
  35. <version>4.12</version>
  36. </dependency>
  37. <dependency>
  38. <groupId>org.springframework</groupId>
  39. <artifactId>spring-test</artifactId>
  40. <version>5.1.5.RELEASE</version>
  41. </dependency>
  42. </dependencies>

entity dao

  1. public class Account {
  2. private Integer id;
  3. private String name;
  4. private Double money;
  5. // setter getter....
  6. }
  7. public interface AccountDao {
  8. public void out(String outUser, Double money);
  9. public void in(String inUser, Double money);
  10. }
  11. @Repository
  12. public class AccountDaoImpl implements AccountDao {
  13. @Autowired
  14. private JdbcTemplate jdbcTemplate;
  15. @Override
  16. public void out(String outUser, double money) {
  17. String sql = "update account set money = money - ? where name = ?";
  18. jdbcTemplate.update(sql, money, outUser);
  19. }
  20. @Override
  21. public void in(String inUser, double money) {
  22. String sql = "update account set money = money + ? where name = ?";
  23. jdbcTemplate.update(sql, money, inUser);
  24. }
  25. }
  1. service
  1. public interface AccountService {
  2. void transfer(String outUser, String inUser, double money);
  3. }
  4. @Service
  5. public class AccountServiceImpl implements AccountService {
  6. @Autowired
  7. private AccountDao accountDao;
  8. @Override
  9. public void transfer(String outUser, String inUser, double money) {
  10. accountDao.out(outUser, money);
  11. accountDao.in(inUser, money);
  12. }
  13. }
  1. 核心配置文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="
  7. http://www.springframework.org/schema/beans
  8. http://www.springframework.org/schema/beans/spring-beans.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd
  11. http://www.springframework.org/schema/aop
  12. http://www.springframework.org/schema/aop/spring-aop.xsd">
  13. <context:component-scan base-package="com.ning"/>
  14. <context:property-placeholder location="classpath:jdbc.properties"/>
  15. <aop:aspectj-autoproxy/>
  16. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  17. <property name="driverClassName" value="${jdbc.driverClassName}"/>
  18. <property name="url" value="${jdbc.url}"/>
  19. <property name="username" value="${jdbc.username}"/>
  20. <property name="password" value="${jdbc.password}"/>
  21. </bean>
  22. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  23. <constructor-arg name="dataSource" ref="dataSource"/>
  24. </bean>
  25. </beans>
  1. 测试代码
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration({"classpath:applicationContext.xml"})
  3. public class AccountServiceImplTest {
  4. @Autowired
  5. private AccountService accountService;
  6. @Test
  7. public void testTransfer() {
  8. accountService.transfer("tom", "jerry", 100d);
  9. }
  10. }
  1. <br />到目前为止其实正常情况下是实现了功能的,唯一的问题在于事务控制。现在的in和out是两个事务。

事务

Spring的事务控制可以分为编程式事务控制和声明式事务控制。
编程式
开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不用。
声明式
开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想。

编程式事务

  1. PlatformTransactionManager接口,是spring的事务管理器,里面提供了我们常用的操作事务的方法。 <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12718322/1635102565290-f86f1e4e-e5d1-43c3-8a7c-33463717013f.png#clientId=ubd96acc2-8a08-4&from=paste&height=134&id=ucf8dbd6f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=267&originWidth=1180&originalType=binary&ratio=1&size=60237&status=done&style=none&taskId=ube6a54ba-3155-42f0-88e2-b6b749fde3c&width=590)
  1. * PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类。
  2. * Dao层技术是jdbcTemplatemybatis时:
  3. DataSourceTransactionManager
  4. * Dao层技术是hibernate时:
  5. HibernateTransactionManager
  6. * Dao层技术是JPA时:
  7. JpaTransactionManager

TransactionDefinition

TransactionDefinition接口提供事务的定义信息(事务隔离级别、事务传播行为等等)
image.png

隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读(幻读)。

  1. * ISOLATION_DEFAULT 使用数据库默认级别
  2. * ISOLATION_READ_UNCOMMITTED 读未提交
  3. * ISOLATION_READ_COMMITTED 读已提交
  4. * ISOLATION_REPEATABLE_READ 可重复读
  5. * ISOLATION_SERIALIZABLE 串行化

事务传播行为

事务传播行为指的就是当一个业务方法【被】另一个业务方法调用时,应该如何进行事务控制。
image.png
查询一般设计成SUPPORTS 增删改用REQUIRED

事务状态

TransactionStatus 接口提供的是事务具体的运行状态
image.png
可以简单的理解三者的关系:事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事 务状态。

声明式事务

声明式事务控制明确事项:
核心业务代码(目标对象) (切入点是谁?)
事务增强代码(Spring已提供事务管理器))(通知是谁?)
切面配置(切面如何配置?)

例子

使用声明式事务完成案例

  1. 引入tx命名空间
  2. 事务管理器通知配置
  3. 事务管理器AOP配置
  4. 测试事务控制转账业务代码
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xmlns:tx="http://www.springframework.org/schema/tx"
  7. xsi:schemaLocation="
  8. http://www.springframework.org/schema/beans
  9. http://www.springframework.org/s chema/beans/spring-beans.xsd
  10. http://www.springframework.org/schema/context
  11. http://www.springframework.org/schema/context/spring-context.xsd
  12. http://www.springframework.org/schema/aop
  13. http://www.springframework.org/schema/aop/spring-aop.xsd
  14. http://www.springframework.org/schema/tx
  15. http://www.springframework.org/schema/tx/spring-tx.xsd">
  16. <context:component-scan base-package="com.ning"/>
  17. <context:property-placeholder location="classpath:jdbc.properties"/>
  18. <aop:aspectj-autoproxy/>
  19. <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  20. <property name="driverClassName" value="${jdbc.driverClassName}"/>
  21. <property name="url" value="${jdbc.url}"/>
  22. <property name="username" value="${jdbc.username}"/>
  23. <property name="password" value="${jdbc.password}"/>
  24. </bean>
  25. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  26. <constructor-arg name="dataSource" ref="dataSource"/>
  27. </bean>
  28. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  29. <constructor-arg name="dataSource" ref="dataSource"/>
  30. </bean>
  31. <tx:advice id="txAdvice" transaction-manager="transactionManager">
  32. <tx:attributes>
  33. <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"/>
  34. </tx:attributes>
  35. </tx:advice>
  36. <aop:config>
  37. <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ning.service.impl.AccountServiceImpl.*(..))"/>
  38. </aop:config>
  39. </beans>

CRUD常用配置

  1. <tx:attributes>
  2. <tx:method name="save*" propagation="REQUIRED"/>
  3. <tx:method name="delete*" propagation="REQUIRED"/>
  4. <tx:method name="update*" propagation="REQUIRED"/>
  5. <tx:method name="find*" read-only="true"/>
  6. <tx:method name="*"/>
  7. </tx:attributes>

注解方式

在你想加事务的方法前面加这个注解

  1. @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false)
  1. 注意,注解可以加在类上。

然后修改配置文件,加这个

  1. <tx:annotation-driven/>

纯注解方式

  1. @Configuration
  2. @ComponentScan("com.ning")
  3. @Import(DataSourceConfig.class)
  4. @EnableTransactionManagement
  5. public class SpringConfig {
  6. @Bean
  7. public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
  8. return new JdbcTemplate(dataSource);
  9. }
  10. @Bean
  11. public PlatformTransactionManager getPlatformTransactionManager(@Autowired DataSource dataSource) {
  12. return new DataSourceTransactionManager(dataSource);
  13. }
  14. }
  15. @PropertySource("classpath:jdbc.properties")
  16. public class DataSourceConfig {
  17. @Value("${jdbc.driverClassName}")
  18. private String driver;
  19. @Value("${jdbc.url}")
  20. private String url;
  21. @Value("${jdbc.username}")
  22. private String username;
  23. @Value("${jdbc.password}")
  24. private String password;
  25. @Bean
  26. public DataSource getDataSource() {
  27. final DruidDataSource dataSource = new DruidDataSource();
  28. dataSource.setDriverClassName(driver);
  29. dataSource.setUrl(url);
  30. dataSource.setUsername(username);
  31. dataSource.setPassword(password);
  32. return dataSource;
  33. }
  34. }

别忘了修改测试类上面的ContextConfiguration注解。

在Servlet中避免多次加载上下文对象

应用上下文对象是通过 new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写 new ClasspathXmlApplicationContext(spring配置文件) , 这样的弊端是配置文件加载多次,应用上下文对象创建多次。

解决思路分析:
在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域 servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。

Spring提供获取应用上下文的工具

上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封 装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供 了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。
所以我们需要做的只有两件事:

  1. 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
  2. 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext
  1. <!--全局参数-->
  2. <context-param>
  3. <param-name>contextConfigLocation</param-name>
  4. <param-value>classpath:applicationContext.xml</param-value>
  5. </context-param>
  6. <!--Spring的监听器-->
  7. <listener>
  8. <listener-class>
  9. org.springframework.web.context.ContextLoaderListener
  10. </listener-class>
  11. </listener>
  1. public class AccountServlet extends HttpServlet {
  2. @Override
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  4. this.doPost(req, resp);
  5. }
  6. @Override
  7. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  8. final WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
  9. final Account account = (Account)context.getBean("account");
  10. System.out.println(account);
  11. }
  12. }