第一章:Spring对JDBC的封装
1.1 JdbcTemplate
1.1.1 概述
1.1.1.1 基本介绍
- Spring对数据库的操作在JDBC上面做了基本的封装,让开发者在操作数据库的时候只需要关注SQL语句和查询结果处理器,即可完成功能。
- 在配合Spring的IOC功能,可以把DataSource注册到JdbcTemplate中,同时利用Spring基于AOP的事务即可完成简单的数据库的CRUD操作。
1.1.1.2 源码
- JdbcTemplate:
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";/** If this variable is false, we will throw exceptions on SQL warnings. */private boolean ignoreWarnings = true;/*** If this variable is set to a non-negative value, it will be used for setting the* fetchSize property on statements used for query processing.*/private int fetchSize = -1;/*** If this variable is set to a non-negative value, it will be used for setting the* maxRows property on statements used for query processing.*/private int maxRows = -1;/*** If this variable is set to a non-negative value, it will be used for setting the* queryTimeout property on statements used for query processing.*/private int queryTimeout = -1;/*** If this variable is set to true, then all results checking will be bypassed for any* callable statement processing. This can be used to avoid a bug in some older Oracle* JDBC drivers like 10.1.0.2.*/private boolean skipResultsProcessing = false;/*** If this variable is set to true then all results from a stored procedure call* that don't have a corresponding SqlOutParameter declaration will be bypassed.* All other results processing will be take place unless the variable* {@code skipResultsProcessing} is set to {@code true}.*/private boolean skipUndeclaredResults = false;/*** If this variable is set to true then execution of a CallableStatement will return* the results in a Map that uses case insensitive names for the parameters.*/private boolean resultsMapCaseInsensitive = false;/*** Construct a new JdbcTemplate for bean usage.* <p>Note: The DataSource has to be set before using the instance.* @see #setDataSource*/public JdbcTemplate() {}/*** Construct a new JdbcTemplate, given a DataSource to obtain connections from.* <p>Note: This will not trigger initialization of the exception translator.* @param dataSource the JDBC DataSource to obtain connections from*/public JdbcTemplate(DataSource dataSource) {setDataSource(dataSource);afterPropertiesSet();}/*** Construct a new JdbcTemplate, given a DataSource to obtain connections from.* <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator* will be triggered.* @param dataSource the JDBC DataSource to obtain connections from* @param lazyInit whether to lazily initialize the SQLExceptionTranslator*/public JdbcTemplate(DataSource dataSource, boolean lazyInit) {setDataSource(dataSource);setLazyInit(lazyInit);afterPropertiesSet();}//略}
1.1.1.3 方法说明
- JdbcTemplate主要提供以下的方法:
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句。
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句。
- query方法及queryForXxx方法:用于执行查询相关语句。
- call方法:用于执行存储过程、函数相关语句。
1.1.2 入门案例
- 导入相关jar包的Maven坐标:
<properties><!-- 5.2.7.RELEASE --><spring.version>5.2.7.RELEASE</spring.version></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>${spring.version}</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.23</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>${spring.version}</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>${spring.version}</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version></dependency><!-- 导入YAML解析工厂坐标 --><dependency><groupId>org.yaml</groupId><artifactId>snakeyaml</artifactId><version>1.26</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version><scope>test</scope></dependency><dependency><groupId>javax.inject</groupId><artifactId>javax.inject</artifactId><version>1</version></dependency><dependency><groupId>javax.annotation</groupId><artifactId>javax.annotation-api</artifactId><version>1.3.2</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version></dependency></dependencies>
- sql脚本:
DROP TABLE IF EXISTS `account`;CREATE TABLE `account` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`money` double NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
- 日志文件log4j2.xml
<?xml version="1.0" encoding="UTF-8"?><!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --><!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--><configuration status="INFO"><!--先定义所有的appender--><appenders><!--输出日志信息到控制台--><console name="Console" target="SYSTEM_OUT"><!--控制日志输出的格式--><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/></console></appenders><!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--><!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出--><loggers><root level="info"><appender-ref ref="Console"/></root></loggers></configuration>
- jdbc.yml
jdbc:driver: com.mysql.cj.jdbc.Driverpassword: 123456url: jdbc:mysql://192.168.2.112:3306/spring5?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=trueuser: root
- 自定义YAMLPropertySourceFactory:
package com.sunxiaping.spring5.factory;import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;import org.springframework.core.env.PropertiesPropertySource;import org.springframework.core.env.PropertySource;import org.springframework.core.io.support.EncodedResource;import org.springframework.core.io.support.PropertySourceFactory;import java.io.IOException;import java.util.Properties;/*** 自定义YAMLPropertySourceFactory*/public class YAMLPropertySourceFactory implements PropertySourceFactory {@Overridepublic PropertySource<?> createPropertySource(String name, EncodedResource encodedResource) throws IOException {YamlPropertiesFactoryBean bean = new YamlPropertiesFactoryBean();bean.setResources(encodedResource.getResource());Properties properties = bean.getObject();return (name != null ? new PropertiesPropertySource(name, properties) : new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties));}}
- 编写实体类:
package com.sunxiaping.spring5.domain;import java.io.Serializable;public class Account implements Serializable {private Integer id;private String name;private Double money;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Double getMoney() {return money;}public void setMoney(Double money) {this.money = money;}@Overridepublic String toString() {return "Account{" +"id=" + id +", name='" + name + '\'' +", money=" + money +'}';}}
- JdbcConfig.java
package com.sunxiaping.spring5.config;import com.alibaba.druid.pool.DruidDataSource;import com.sunxiaping.spring5.factory.YAMLPropertySourceFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;import org.springframework.jdbc.core.JdbcTemplate;import javax.sql.DataSource;@Configuration@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.user}")private String user;@Value("${jdbc.password}")private String password;/*** 配置数据源** @return 数据源*/@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);return dataSource;}/*** 配置JdbcTemplate** @param dataSource 数据源* @return JdbcTemplate*/@Beanpublic JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);}}
- SpringConfig.java
package com.sunxiaping.spring5.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/*** Spring的配置类*/@Configuration@ComponentScan(value = "com.sunxiaping.spring5")@Import(value = JdbcConfig.class)public class SpringConfig {}
- AccountDao.java
package com.sunxiaping.spring5.dao;import com.sunxiaping.spring5.domain.Account;import java.util.List;public interface AccountDao {/*** 保存账户信息** @param account*/void saveAccount(Account account);/*** 更新账户信息** @param account*/void updateAccount(Account account);/*** 删除账户信息** @param id*/void deleteAccount(Integer id);/*** 根据id查询账户信息** @param id* @return*/Account findById(Integer id);/*** 查询全部账户信息** @return*/List<Account> findAll();/*** 查询数量** @return*/Long findCount();}
- AccountDaoImpl.java
package com.sunxiaping.spring5.dao.impl;import com.sunxiaping.spring5.dao.AccountDao;import com.sunxiaping.spring5.domain.Account;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.SingleColumnRowMapper;import org.springframework.stereotype.Repository;import java.util.List;@Repositorypublic class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void saveAccount(Account account) {jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?) ", account.getName(), account.getMoney());}@Overridepublic void updateAccount(Account account) {jdbcTemplate.update(" UPDATE `account` SET `name` =?,`money` =? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());}@Overridepublic void deleteAccount(Integer id) {jdbcTemplate.update(" DELETE FROM `account` WHERE id = ? ", id);}@Overridepublic Account findById(Integer id) {return jdbcTemplate.queryForObject("SELECT * FROM `account` WHERE id= ?", new BeanPropertyRowMapper<>(Account.class), id);}@Overridepublic List<Account> findAll() {return jdbcTemplate.query("SELECT * FROM `account`",new BeanPropertyRowMapper<>(Account.class));}@Overridepublic Long findCount() {return jdbcTemplate.queryForObject(" SELECT count(*) FROM `account` ",new SingleColumnRowMapper<>());}}
- 测试:
package com.sunxiaping.spring5;import com.sunxiaping.spring5.config.SpringConfig;import com.sunxiaping.spring5.dao.AccountDao;import com.sunxiaping.spring5.domain.Account;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.List;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfig.class)public class Spring5Test {@Autowiredprivate AccountDao accountDao;@Testpublic void testSaveAccount(){Account account = new Account();account.setName("张三");account.setMoney(1.00);accountDao.saveAccount(account);}@Testpublic void testUpdateAccount(){Account account = accountDao.findById(1);account.setMoney(2.00);accountDao.updateAccount(account);}@Testpublic void testDeleteAccount(){Account account = accountDao.findById(1);if(null != account){accountDao.deleteAccount(1);}}@Testpublic void testFindById(){Account account = accountDao.findById(1);System.out.println("account = " + account);}@Testpublic void testFindAll(){List<Account> accountList = accountDao.findAll();System.out.println("accountList = " + accountList);}@Testpublic void testFindCount(){Long count = accountDao.findCount();System.out.println("count = " + count);}}
1.2 NamedParameterJdbcTemplate
1.2.1 概述
1.2.1.1 基本介绍
- 在经典的JDBC用法中,SQL的参数是用占位符?表示的,并且受到位置的限制。定位参数的问题在于,一旦参数的位置发生改变,就必须改变参数绑定。在Spring JDBC框架中,绑定SQL参数的另一种选择是使用具名参数。
- 具名参数:SQL按名称而不是按照位置进行指定。具名参数更易于维护,也提升了可读性,具名参数由框架类在运行时用占位符取代。
1.2.1.2 源码
- NamedParameterJdbcTemplate:
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {/** Default maximum number of entries for this template's SQL cache: 256. */public static final int DEFAULT_CACHE_LIMIT = 256;/** The JdbcTemplate we are wrapping. */private final JdbcOperations classicJdbcTemplate;private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;/** Cache of original SQL String to ParsedSql representation. */@SuppressWarnings("serial")private final Map<String, ParsedSql> parsedSqlCache =new LinkedHashMap<String, ParsedSql>(DEFAULT_CACHE_LIMIT, 0.75f, true) {@Overrideprotected boolean removeEldestEntry(Map.Entry<String, ParsedSql> eldest) {return size() > getCacheLimit();}};/*** Create a new NamedParameterJdbcTemplate for the given {@link DataSource}.* <p>Creates a classic Spring {@link org.springframework.jdbc.core.JdbcTemplate} and wraps it.* @param dataSource the JDBC DataSource to access*/public NamedParameterJdbcTemplate(DataSource dataSource) {Assert.notNull(dataSource, "DataSource must not be null");this.classicJdbcTemplate = new JdbcTemplate(dataSource);}//略}
1.2.2 入门案例
- JdbcConfig.java
package com.sunxiaping.spring5.config;import com.alibaba.druid.pool.DruidDataSource;import com.sunxiaping.spring5.factory.YAMLPropertySourceFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;import javax.sql.DataSource;@Configuration@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.user}")private String user;@Value("${jdbc.password}")private String password;/*** 配置数据源** @return 数据源*/@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);return dataSource;}/*** 配置JdbcTemplate** @param dataSource 数据源* @return JdbcTemplate*/@Beanpublic JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);}/*** 配置NamedParameterJdbcTemplate** @param jdbcTemplate* @return*/@Beanpublic NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {return new NamedParameterJdbcTemplate(jdbcTemplate);}}
- SpringConfig.java
package com.sunxiaping.spring5.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;/*** Spring的配置类*/@Configuration@ComponentScan(value = "com.sunxiaping.spring5")@Import(value = JdbcConfig.class)public class SpringConfig {}
- 测试:
package com.sunxiaping.spring5;import com.sunxiaping.spring5.config.SpringConfig;import com.sunxiaping.spring5.domain.Account;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cglib.beans.BeanMap;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.util.HashMap;import java.util.Map;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfig.class)public class Spring5Test {@Autowiredprivate NamedParameterJdbcTemplate namedParameterJdbcTemplate;@Testpublic void testSaveAccount() {Account account = new Account();account.setName("李四");account.setMoney(1d);BeanMap beanMap = BeanMap.create(account);namedParameterJdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (:name,:money) ",beanMap);}@Testpublic void testFindById(){Map<String,Object> map = new HashMap<>();map.put("id",1);Account account = namedParameterJdbcTemplate.queryForObject(" SELECT * FROM `account` WHERE id = :id ", map, new BeanPropertyRowMapper<>(Account.class));System.out.println("account = " + account);}}
第二章:Spring中的事务
2.1 API介绍
2.1.1 PlatformTransactionManager和其实现类
作用:此接口是Spring的事务管理器的核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。在Spring框架中,也为我们内置了一些具体策略,例如:DatasourceTransactionManager、HibernateTransactionManager、jpaTransactionManager等。
类图:

- PlatformTransactionManager:
public interface PlatformTransactionManager extends TransactionManager {/*** 获取事务状态信息*/TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;/*** 提交事务*/void commit(TransactionStatus status) throws TransactionException;/*** 回滚事务*/void rollback(TransactionStatus status) throws TransactionException;}
2.1.2 TransactionDefinition
作用:此接口是Spring中事务可控属性的顶层接口,里面定义了事务的一些属性以及获取属性的方法。例如:事务的传播行为、事务的隔离级别、事务的只读、事务的超时等待。通常情况下,我们在开发洪都可以配置这些属性,以求达到最佳效果。
类图:

- TransactionDefinition:
public interface TransactionDefinition {/*** REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)*/int PROPAGATION_REQUIRED = 0;/*** SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)*/int PROPAGATION_SUPPORTS = 1;/*** MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常*/int PROPAGATION_MANDATORY = 2;/*** REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。*/int PROPAGATION_REQUIRES_NEW = 3;/*** NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起*/int PROPAGATION_NOT_SUPPORTED = 4;/*** NEVER:以非事务方式运行,如果当前存在事务,抛出异常*/int PROPAGATION_NEVER = 5;/*** NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。*/int PROPAGATION_NESTED = 6;/*** 事务的隔离级别默认值,当取值-1时,会采用下面的4个值其中一个。* 事务的隔离级别默认值,当取值-1时,会采用下面的4个值其中一个。*/int ISOLATION_DEFAULT = -1;/*** 事务隔离级别为:读未提交* 事务隔离级别为:读未提交*/int ISOLATION_READ_UNCOMMITTED = 1;/*** 事务隔离级别为:读已提交* 可以防止脏读的发生,但是无法防住不可重复读和幻读的发生*/int ISOLATION_READ_COMMITTED = 2;/*** 事务隔离级别为:可重复读* 可以防止脏读和不可重复读的发生,但是无法防住幻读的发生*/int ISOLATION_REPEATABLE_READ = 4;/*** 事务隔离级别为:串行化* 此时所有错误情况均可防住,但是由于事务变成了独占模式(排他模式),因此效率最低*/int ISOLATION_SERIALIZABLE = 8;/*** 超时限制。默认值是-1,没有超时限制。如果有,以秒为单位进行设置。*/int TIMEOUT_DEFAULT = -1;/*** 获取事务传播行为*/default int getPropagationBehavior() {return PROPAGATION_REQUIRED;}/*** 获取事务隔离级别*/default int getIsolationLevel() {return ISOLATION_DEFAULT;}/*** 获取事务超时时间*/default int getTimeout() {return TIMEOUT_DEFAULT;}/*** 获取事务是否只读*/default boolean isReadOnly() {return false;}/*** 获取事务名称*/@Nullabledefault String getName() {return null;}// Static builder methods/*** Return an unmodifiable {@code TransactionDefinition} with defaults.* <p>For customization purposes, use the modifiable* {@link org.springframework.transaction.support.DefaultTransactionDefinition}* instead.* @since 5.2*/static TransactionDefinition withDefaults() {return StaticTransactionDefinition.INSTANCE;}}
2.1.3 TransactionStatus
作用:此接口是事务运行状态表示的顶层接口,里面定义着获取事务运行状态的一些方法。
类图:

- TransactionStatus:
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {/*** 是否包含存储点*/boolean hasSavepoint();/*** 刷新事务*/@Overridevoid flush();}
2.2 入门案例
- JdbcConfig.java
package com.sunxiaping.spring5.config;import com.alibaba.druid.pool.DruidDataSource;import com.sunxiaping.spring5.factory.YAMLPropertySourceFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.TransactionManager;import javax.sql.DataSource;@Configuration@PropertySource(value = "classpath:jdbc.yml", factory = YAMLPropertySourceFactory.class)public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.user}")private String user;@Value("${jdbc.password}")private String password;/*** 配置数据源** @return 数据源*/@Beanpublic DataSource dataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName(driver);dataSource.setUrl(url);dataSource.setUsername(user);dataSource.setPassword(password);return dataSource;}/*** 配置JdbcTemplate** @param dataSource 数据源* @return JdbcTemplate*/@Beanpublic JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);}/*** 配置NamedParameterJdbcTemplate** @param jdbcTemplate* @return*/@Beanpublic NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {return new NamedParameterJdbcTemplate(jdbcTemplate);}/*** 配置事务管理器** @param dataSource* @return*/@Beanpublic TransactionManager transactionManager(@Autowired DataSource dataSource) {DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource);return dataSourceTransactionManager;}}
- SpringConfig.java
package com.sunxiaping.spring5.config;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Import;import org.springframework.transaction.annotation.EnableTransactionManagement;/*** Spring的配置类*/@Configuration@ComponentScan(value = "com.sunxiaping.spring5")@Import(value = JdbcConfig.class)@EnableTransactionManagementpublic class SpringConfig {}
- AccountDao.java
package com.sunxiaping.spring5.dao;import com.sunxiaping.spring5.domain.Account;import java.util.List;public interface AccountDao {/*** 保存账户信息** @param account*/void saveAccount(Account account);/*** 更新账户信息** @param account*/void updateAccount(Account account);/*** 删除账户信息** @param id*/void deleteAccount(Integer id);/*** 根据id查询账户信息** @param id* @return*/Account findById(Integer id);/*** 查询全部账户信息** @return*/List<Account> findAll();/*** 查询数量** @return*/Long findCount();/*** 根据名称查询账户信息** @param name* @return*/Account findByName(String name);}
- AccountDaoImpl.java
package com.sunxiaping.spring5.dao.impl;import com.sunxiaping.spring5.dao.AccountDao;import com.sunxiaping.spring5.domain.Account;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.BeanPropertyRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.core.SingleColumnRowMapper;import org.springframework.stereotype.Repository;import org.springframework.util.CollectionUtils;import org.springframework.util.StringUtils;import java.util.List;@Repositorypublic class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void saveAccount(Account account) {jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?) ", account.getName(), account.getMoney());}@Overridepublic void updateAccount(Account account) {jdbcTemplate.update(" UPDATE `account` SET `name` =?,`money` =? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());}@Overridepublic void deleteAccount(Integer id) {jdbcTemplate.update(" DELETE FROM `account` WHERE id = ? ", id);}@Overridepublic Account findById(Integer id) {return jdbcTemplate.queryForObject("SELECT * FROM `account` WHERE id= ?", new BeanPropertyRowMapper<>(Account.class), id);}@Overridepublic List<Account> findAll() {return jdbcTemplate.query("SELECT * FROM `account`", new BeanPropertyRowMapper<>(Account.class));}@Overridepublic Long findCount() {return jdbcTemplate.queryForObject(" SELECT count(*) FROM `account` ", new SingleColumnRowMapper<>());}@Overridepublic Account findByName(String name) {if (StringUtils.isEmpty(name)) {return null;}List<Account> accountList = jdbcTemplate.query("SELECT * FROM `account` WHERE `name` =?", new BeanPropertyRowMapper<>(Account.class), name);if (CollectionUtils.isEmpty(accountList)) {return null;}if (accountList.size() > 1) {throw new RuntimeException(name + "在数据库中不止一个");}return accountList.get(0);}}
- AccountService.java
package com.sunxiaping.spring5.service;public interface AccountService {/*** 转账** @param sourceName 转出人* @param targetName 转入人* @param money 金额*/void transfer(String sourceName, String targetName, Double money);}
- AccountServiceImpl.java
package com.sunxiaping.spring5.service.impl;import com.sunxiaping.spring5.dao.AccountDao;import com.sunxiaping.spring5.domain.Account;import com.sunxiaping.spring5.service.AccountService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;import org.springframework.util.ObjectUtils;@Service@Transactionalpublic class AccountServiceImpl implements AccountService {@Autowiredprivate AccountDao accountDao;@Overridepublic void transfer(String sourceName, String targetName, Double money) {Account source = accountDao.findByName(sourceName);Account target = accountDao.findByName(targetName);if (!ObjectUtils.isEmpty(source) && !ObjectUtils.isEmpty(target)) {source.setMoney(source.getMoney() - money);target.setMoney(target.getMoney() + money);accountDao.updateAccount(source);//模拟异常int num = 10 / 0;accountDao.updateAccount(target);}}}
- 测试:
package com.sunxiaping.spring5;import com.sunxiaping.spring5.config.SpringConfig;import com.sunxiaping.spring5.service.AccountService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.test.context.ContextConfiguration;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = SpringConfig.class)public class Spring5Test {@Autowiredprivate AccountService accountService;@Testpublic void test() {accountService.transfer("张三","李四",100d);}}
