1 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 源码
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 入门案例
<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>
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;
<?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:
driver: com.mysql.cj.jdbc.Driver
password: 123456
url: jdbc:mysql://192.168.2.112:3306/spring5?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
user: 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 {
@Override
public 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;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
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 数据源
*/
@Bean
public 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
*/
@Bean
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
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.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();
}
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;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void saveAccount(Account account) {
jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?) ", account.getName(), account.getMoney());
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update(" UPDATE `account` SET `name` =?,`money` =? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());
}
@Override
public void deleteAccount(Integer id) {
jdbcTemplate.update(" DELETE FROM `account` WHERE id = ? ", id);
}
@Override
public Account findById(Integer id) {
return jdbcTemplate.queryForObject("SELECT * FROM `account` WHERE id= ?", new BeanPropertyRowMapper<>(Account.class), id);
}
@Override
public List<Account> findAll() {
return jdbcTemplate.query("SELECT * FROM `account`",new BeanPropertyRowMapper<>(Account.class));
}
@Override
public 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 {
@Autowired
private AccountDao accountDao;
@Test
public void testSaveAccount(){
Account account = new Account();
account.setName("张三");
account.setMoney(1.00);
accountDao.saveAccount(account);
}
@Test
public void testUpdateAccount(){
Account account = accountDao.findById(1);
account.setMoney(2.00);
accountDao.updateAccount(account);
}
@Test
public void testDeleteAccount(){
Account account = accountDao.findById(1);
if(null != account){
accountDao.deleteAccount(1);
}
}
@Test
public void testFindById(){
Account account = accountDao.findById(1);
System.out.println("account = " + account);
}
@Test
public void testFindAll(){
List<Account> accountList = accountDao.findAll();
System.out.println("accountList = " + accountList);
}
@Test
public 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) {
@Override
protected 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 入门案例
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 数据源
*/
@Bean
public 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
*/
@Bean
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 配置NamedParameterJdbcTemplate
*
* @param jdbcTemplate
* @return
*/
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
}
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 {
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Test
public 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);
}
@Test
public 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);
}
}
2 Spring中的事务
2.1 API介绍
2.1.1.1 作用
- 此接口是Spring的事务管理器的核心接口。Spring本身并不支持事务实现,只是负责提供标准,应用底层支持什么样的事务,需要提供具体实现类。在Spring框架中,也为我们内置了一些具体策略,例如:DatasourceTransactionManager、HibernateTransactionManager、jpaTransactionManager等。
2.1.1.2 类图
2.1.1.3 方法说明
- 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
2.1.2.1 作用
- 此接口是Spring中事务可控属性的顶层接口,里面定义了事务的一些属性以及获取属性的方法。例如:事务的传播行为、事务的隔离级别、事务的只读、事务的超时等待。通常情况下,我们在开发洪都可以配置这些属性,以求达到最佳效果。
2.1.2.2 类图
2.1.2.3 定义信息说明
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;
}
/**
* 获取事务名称
*/
@Nullable
default 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
2.1.3.1 作用
- 此接口是事务运行状态表示的顶层接口,里面定义着获取事务运行状态的一些方法。
2.1.3.2 类图
2.1.3.3 方法说明
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
/**
* 是否包含存储点
*/
boolean hasSavepoint();
/**
* 刷新事务
*/
@Override
void flush();
}
2.2 入门案例
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 数据源
*/
@Bean
public 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
*/
@Bean
public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 配置NamedParameterJdbcTemplate
*
* @param jdbcTemplate
* @return
*/
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
return new NamedParameterJdbcTemplate(jdbcTemplate);
}
/**
* 配置事务管理器
*
* @param dataSource
* @return
*/
@Bean
public TransactionManager transactionManager(@Autowired DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
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)
@EnableTransactionManagement
public class SpringConfig {
}
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);
}
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;
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void saveAccount(Account account) {
jdbcTemplate.update(" INSERT INTO `account` (`name`,`money`) VALUES (?,?) ", account.getName(), account.getMoney());
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update(" UPDATE `account` SET `name` =?,`money` =? WHERE id = ? ", account.getName(), account.getMoney(), account.getId());
}
@Override
public void deleteAccount(Integer id) {
jdbcTemplate.update(" DELETE FROM `account` WHERE id = ? ", id);
}
@Override
public Account findById(Integer id) {
return jdbcTemplate.queryForObject("SELECT * FROM `account` WHERE id= ?", new BeanPropertyRowMapper<>(Account.class), id);
}
@Override
public List<Account> findAll() {
return jdbcTemplate.query("SELECT * FROM `account`", new BeanPropertyRowMapper<>(Account.class));
}
@Override
public Long findCount() {
return jdbcTemplate.queryForObject(" SELECT count(*) FROM `account` ", new SingleColumnRowMapper<>());
}
@Override
public 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);
}
}
package com.sunxiaping.spring5.service;
public interface AccountService {
/**
* 转账
*
* @param sourceName 转出人
* @param targetName 转入人
* @param money 金额
*/
void transfer(String sourceName, String targetName, Double money);
}
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
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public 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 {
@Autowired
private AccountService accountService;
@Test
public void test() {
accountService.transfer("张三","李四",100d);
}
}