04-apply
propagation 英 [ˌprɒpə'ɡeɪʃ(ə)n] n. 传播;扩展;宣传;培养
mandatory 英 [ˈmændətəri] adj. 强制性的;强制的;法定的;义务的
Spring框架提供了很多操作模板类:
JdbcTemplate
(操作关系型数据)RedisTemplate
(操作nosql数据库)JmsTemplate
(操作消息队列)
1. JDBC
JdbcTemplate
是Spring框架提供的对象,是对原始繁琐的JDBC_API的简单封装
1. pom.xml
<!-- spring-jdbc包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.5</version>
</dependency>
<!-- spring事务控制包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.5</version>
</dependency>
<!-- spring-orm映射依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.5</version>
</dependency>
2. druid.properties
jdbc_driver=com.mysql.cj.jdbc.Driver
jdbc_url=jdbc:mysql://ip:port/mca?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc_username=******
jdbc_password=******
password=ooxx
age=18
3. x8_jdbc.xml
<!-- Spring注解扫描 -->
<context:component-scan base-package="com.listao.spring.jdbc"/>
<!-- 读取jdbc配置文件 -->
<context:property-placeholder location="classpath:druid.properties"/>
<!-- 德鲁伊连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
<property name="url" value="${jdbc_url}"/>
<property name="driverClassName" value="${jdbc_driver}"/>
</bean>
<!-- 配置JdbcTemplate对象,并注入DataSource -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- set()注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
4. JdbcDao
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Jdbc {
private String id;
private String name;
private Integer age;
private String address;
private Date updTime;
}
@Repository
public class JdbcDao implements IJdbc {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Integer count() {
/*
* queryForObject()
* 1. sql
* 2. 返回值类型
*/
return jdbcTemplate.queryForObject("select count(*) from spring_jdbc", Integer.class);
}
@Override
public Jdbc selById(String id) {
String sql = "select * from spring_jdbc where id = ?";
BeanPropertyRowMapper<Jdbc> rowMapper = new BeanPropertyRowMapper<>(Jdbc.class);
try {
/*
* 1. sql
* 2. 返回值类型。RowMapper接口实现类
* 3. sql入参
*/
return jdbcTemplate.queryForObject(sql, rowMapper, id);
} catch (DataAccessException e) {
// EmptyResultDataAccessException
e.printStackTrace();
return null;
}
}
@Override
public List<Jdbc> selByAge(int age) {
String sql = "select * from spring_jdbc where age = ?";
BeanPropertyRowMapper<Jdbc> rowMapper = new BeanPropertyRowMapper<>(Jdbc.class);
return jdbcTemplate.query(sql, rowMapper, age);
}
@Override
public int ins(Jdbc tmp) {
/*
* 增加员工信息
* jdbcTemplate.update
* 1. sql
* 2. SQL语句中的需要的参数,可变参数
*/
String sql = "insert into spring_jdbc values(?, ?, ?, ?, ?)";
Object[] args = {tmp.getId(), tmp.getName(), tmp.getAge(), tmp.getAddress(), tmp.getUpdTime()};
return jdbcTemplate.update(sql, args);
}
@Override
public int upd(Jdbc tmp) {
String sql = "update spring_jdbc set name = ? where id = ?";
Object[] args = {tmp.getName(), tmp.getId()};
return jdbcTemplate.update(sql, args);
}
@Override
public int del(String id) {
String sql = "delete from spring_jdbc where id = ?";
return jdbcTemplate.update(sql, id);
}
@Override
public int[] batchIns(List<Jdbc> tmps) {
String sql = "insert into spring_jdbc values(?,?,?,?,?)";
List<Object[]> args = new ArrayList<>();
for (Jdbc tmp : tmps) {
Object[] arg = {tmp.getId(), tmp.getName(), tmp.getAge(), tmp.getAddress(), tmp.getUpdTime()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
@Override
public int[] batchUpd(List<Jdbc> tmps) {
String sql = "update spring_jdbc set name = ? where id = ?";
List<Object[]> args = new LinkedList<>();
for (Jdbc tmp : tmps) {
Object[] arg = {tmp.getName(), tmp.getId()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
@Override
public int[] batchDel(List<Jdbc> tmps) {
String sql = "delete from spring_jdbc where id = ?";
List<Object[]> args = new ArrayList<>();
for (Jdbc tmp : tmps) {
Object[] arg = {tmp.getId()};
args.add(arg);
}
return jdbcTemplate.batchUpdate(sql, args);
}
}
5. Junit
public class T4_Jdbc {
@Test
public void jdbc() {
ApplicationContext ac = new ClassPathXmlApplicationContext("x8_jdbc.xml");
IJdbc iJdbc = ac.getBean("jdbcDao", IJdbc.class);
System.out.println("count() = " + iJdbc.count());
Jdbc byId = iJdbc.selById("1");
System.out.println("selById = " + byId);
List<Jdbc> byAge = iJdbc.selByAge(18);
System.out.println("selByAge = " + byAge);
String insId = UUID.randomUUID().toString();
Jdbc ins = Jdbc.builder()
.id(insId)
.name("ins")
.age(18)
.address("北京,昌平")
.updTime(new Date())
.build();
System.out.println("ins() = " + iJdbc.ins(ins));
Jdbc upd = Jdbc.builder()
.id("1")
.name("upd")
.build();
System.out.println("upd() = " + iJdbc.upd(upd));
System.out.println("del() = " + iJdbc.del(insId));
}
@Test
public void batchIns() {
ApplicationContext ac = new ClassPathXmlApplicationContext("x8_jdbc.xml");
IJdbc iJdbc = ac.getBean(IJdbc.class);
List<Jdbc> jdbcs = new ArrayList<>();
for (int i = 0; i < 10; i++) {
jdbcs.add(new Jdbc(String.valueOf(i), "name" + i, i, String.valueOf(i), new Date()));
}
int[] ints = iJdbc.batchIns(jdbcs);
System.out.println(Arrays.toString(ints));
}
@Test
public void batchUpd() {
ApplicationContext ac = new ClassPathXmlApplicationContext("x8_jdbc.xml");
IJdbc iJdbc = ac.getBean(IJdbc.class);
List<Jdbc> tmps = new ArrayList<>();
for (int i = 0; i < 10; i++) {
tmps.add(new Jdbc(String.valueOf(i), "ooxx", i, String.valueOf(i), new Date()));
}
int[] ints = iJdbc.batchUpd(tmps);
System.out.println(Arrays.toString(ints));
}
@Test
public void batchDel() {
ApplicationContext ac = new ClassPathXmlApplicationContext("x8_jdbc.xml");
IJdbc iJdbc = ac.getBean(IJdbc.class);
List<Jdbc> jdbcs = new ArrayList<>();
for (int i = 0; i < 10; i++) {
jdbcs.add(new Jdbc(String.valueOf(i), "ooxx", i, String.valueOf(i), new Date()));
}
int[] ints = iJdbc.batchDel(jdbcs);
System.out.println(Arrays.toString(ints));
}
}
2. Transaction
- 事务(Transaction)指一个操作序列,该操作序列是一个不可分割的工作单位。是DB环境中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理
- 常用存储引擎InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM,其中InnoDB支持Trx,MyISAM不支持
1. 特性
- 一个操作序列要成为事务,必须满足ACID特性
1. 原子性(Atomicity)
- 原子是自然界最小的颗粒,具有不可再分的特性。事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小逻辑执行体
- 要么全部执行,要么全不执行
2. 一致性(Consistency)
- 事务执行结果必须使DB从一个一致性状态,到另一个一致性状态。当DB中只包含事务成功提交的结果时,BD处于一致性状态。一致性是通过原子性来保证的
- eg:转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配
3. 隔离性(Isolation)
- 指各个事务执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的
- 并发执行的事务之间既不能看到对方的中间状态,也不能相互影响
- eg:转账时,只有当A账户中的转出和B账户中转入操作都执行成功后,才能看到A账户中的金额减少以及B账户中的金额增多。并且其他的事务对于转账操作的事务是不能产生任何影响的
4. 持久性(Durability)
- 持久性指事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存进物理数据库
2. 并发问题
1. 脏读(Dirty_read)
- 一个事务读到了另外一个事务没有提交的“脏数据“
2. 不可重复读(Unrepeatable_read)
- 一个事务(T1)两次读到的数据是不一致,中间被其他事务(T2)修改了
3. 幻读(Phantom_read)
- 一个事务(T1)读取数据为0,另一个并发事务(T2)插入了一些数据时。T1查询不到T2的数据,却没办法插入T2数据
4. Spring实现
- 编程式事务(不推荐)
- 声明式事务(掌握)。底层为AOP,AOP底层为动态代理
- 注解(简单,必会)
- XML配置(繁琐,了解)
- 事务管理应放在Service层进行处理
- 事务管理器接口:
PlatformTransactionManager
,针对不同框架,提供不同实现类- jdbc应用的事务管理类
DataSourceTransactionManager
- jdbc应用的事务管理类
1. @Transactional
- 类上,类中所有方法都添加事务控制
- 方法上,当前方法增加事务控制
- 父类声明的
@Transactional
会对子类的所有方法进行事务增强。子类可重写父类@Transactional
配置 - 类名上使用
@Transactional
,类中方法可重写类上的@Transactional
配置
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.DEFAULT, // 隔离级别
timeout = -1, // 超时时间
readOnly = false, // 是否只读
rollbackFor = NullPointerException.class, // 回滚异常指定
noRollbackFor = ClassCastException.class // 不回滚异常指定
)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
1. propagation
- 事务的传播行为,默认为
REQUIRED
- 多事务方法之间调用,事务如何管理
传播属性 | 描述 |
---|---|
REQUIRED | 加入当前事务;如果没有,新建一个 |
SUPPORTS | 加入当前事务;如果没有,非事务运行 |
MANDATORY | 加入当前事务;如果没有,抛出异常 |
REQUIRED_NEW | 挂起当前事务;新建事务运行 |
NOT SUPPORTS | 挂起当前事务,抛异常;如果没有,非事务运行 |
NEVER | 当前事务,抛异常;如果没有,非事务运行 |
NESTED | 当前事务设置保存点,新建嵌套事务运行。如果没有,新建事务运行 |
2. isolation
- 用于决定如何控制并发用户读写数据的操作
- DB允许多用户并发访问,如果多个用户同时开启事务并对同一数据进行读写操作,可能出现脏读、不可重复读、幻读问题
- 所以MySQL中提供了四种隔离级别来解决并发问题
- DEFAULT(默认)
PlatformTransactionManager
默认的隔离级别,即使用DB默认的事务隔离级别- MySQL默认
REPEATABLE_READ
。Oracle默认READ_COMMITTED
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read_uncommitted | √ | √ | √ |
read_committed | √ | √ | |
repeatable_read | √ | ||
serializable |
3. timeout
- 超时时间
- Trx一定要在多长时间之内提交,如果不提交就回滚
4. readOnly
- 只读Trx
- Trx是否只能读取DB的数据,如果为true,则不允许增删改
5. rollbackFor
- 指定发生回滚的异常
6. noRollbackFor
- 指定不发生回滚的异常
2. xml + anno
xmlns:tx="http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ...>
<context:component-scan base-package="com.listao.spring.transaction"/>
<context:property-placeholder location="classpath:druid.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
<property name="url" value="${jdbc_url}"/>
<property name="driverClassName" value="${jdbc_driver}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 声明一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- setDataSource(@Nullable DataSource dataSource) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务的注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
@Repository
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int transMoney(int id, int money) {
String sql = "update spring_tx set money = money + ? where id = ?";
return jdbcTemplate.update(sql, money, id);
}
}
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao iAccountDao;
@Override
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.DEFAULT, // 隔离级别
timeout = -1, // 超时时间
readOnly = false, // 是否只读
rollbackFor = NullPointerException.class, // 回滚异常指定
noRollbackFor = ClassCastException.class // 不回滚异常指定
)
public int transMoney(int from, int to, int money) {
int rows = 0;
// 转出操作
rows += iAccountDao.transMoney(from, -money);
// 模拟异常
int i = 1 / 0;
// 转入操作
rows += iAccountDao.transMoney(to, money);
return rows;
}
}
public class T5_Tx {
ApplicationContext ac;
@Test
public void xml_anno() {
ac = new ClassPathXmlApplicationContext("x9_tx.xml");
ooxx();
}
@Test
public void xml() {
ac = new ClassPathXmlApplicationContext("x10_tx_xml.xml");
ooxx();
}
@Test
public void anno() {
ac = new AnnotationConfigApplicationContext(Cfg3_Tx.class);
ooxx();
}
private void ooxx() {
IAccountService as = ac.getBean("accountServiceImpl", IAccountService.class);
int rows = as.transMoney(1, 2, 100);
System.out.println("rows = " + rows);
}
}
3. xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" ...>
<context:component-scan base-package="com.listao.transaction"/>
<context:property-placeholder location="classpath:druid.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc_username}"/>
<property name="password" value="${jdbc_password}"/>
<property name="url" value="${jdbc_url}"/>
<property name="driverClassName" value="${jdbc_driver}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 声明一个事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- setDataSource(@Nullable DataSource dataSource) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置通知 -->
<tx:advice id="txAdvice">
<tx:attributes>
<!-- name可以通配 -->
<tx:method name="transMoney"
propagation="REQUIRED"
isolation="DEFAULT"
timeout="-1"
read-only="false"
rollback-for="NullPointerException.class"
no-rollback-for="ClassCastException.class"/>
</tx:attributes>
</tx:advice>
<!-- 配置AOP -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.listao.transaction.IAccountService.transMoney(..))"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
4. anno
- 向IOC容器中注册,依赖的jar中的类时。要
@Configuration
+@Bean
@Configuration // 配置类标志注解
@ComponentScan(basePackages = "com.listao.spring.transaction") // Spring组件扫描
@PropertySource("classpath:druid.properties") // 属性配置文件
@EnableTransactionManagement // 开启事务
public class Cfg3_Tx {
// @Value可以从properties取值
@Value("${jdbc_url}")
private String url;
@Value("${jdbc_driver}")
private String driver;
@Value("${jdbc_username}")
private String username;
@Value("${jdbc_password}")
private String password;
/*
* 1. @Bean => <bean>
* 2. set() => <property>
*/
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource ds = new DruidDataSource();
ds.setPassword(password);
ds.setUsername(username);
ds.setDriverClassName(driver);
ds.setUrl(url);
return ds;
}
/**
* 形参(DataSource dataSource),会自动从容器中根据type注入 => ref
*/
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dataSource);
return jt;
}
@Bean
public PlatformTransactionManager getPlatformTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dtm = new DataSourceTransactionManager();
dtm.setDataSource(dataSource);
return dtm;
}
}
3. log4j2
Spring5框架自带通用日志封装,也可以整合自己的日志
- Spring移除了
LOG4jConfigListener
,官方建议使用log4j2
- Spring5整合
log4j2
- 导入
log4j2
依赖
- 导入
<!-- log4j2 -->
<!--<dependency>-->
<!-- <groupId>org.apache.logging.log4j</groupId>-->
<!-- <artifactId>log4j-core</artifactId>-->
<!-- <version>2.14.0</version>-->
<!--</dependency>-->
<!-- slf4-impl包含了log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.0</version>
</dependency>
resources
目录下log4j2.xml
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
<!-- 指定日志输出方式 -->
<Appenders>
<!-- 控制台输出,SYSTEM_ERR输出工具,红黑字,SYSTEM_ERR黑字 -->
<Console name="Console" target="SYSTEM_ERR">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n"/>
</Console>
<!-- 文件输出 -->
<RollingFile name="RollingFile" filename="log/test.log" filepattern="${logPath}/%d{YYYYMMddHHmmss}-fargo.log">
<PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10 MB"/>
</Policies>
<DefaultRolloverStrategy max="20"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="DEBUG">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
4. Junit
1. Junit4
<!-- junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.5</version>
</dependency>
// 自动生成容器
@RunWith(SpringJUnit4ClassRunner.class)
// IOC配置文件指定
@ContextConfiguration("classpath:x8_jdbc.xml")
public class T6_Junit4 {
@Autowired
private IJdbc iJdbc;
/*
* ApplicationContext ac = new ClassPathXmlApplicationContext("x8_jdbc.xml");
* IJdbc iJdbc = ac.getBean(IJdbc.class);
*/
@Test
public void ooxx() {
System.out.println("iJdbc.count() = " + iJdbc.count());
}
}
2. Junit5
<!-- junit5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
</dependency>
// @ExtendWith(SpringExtension.class)
// @ContextConfiguration("classpath:x8_jdbc.xml")
// 复合注解 @ExtendWith + @ContextConfiguration
@SpringJUnitConfig(locations = "classpath:x8_jdbc.xml")
// @SpringJUnitConfig(Cfg4_Junit.class)
public class T7_Junit5 {
@Autowired
private IJdbc iJdbc;
@Test
public void ooxx() {
System.out.println("iJdbc.count() = " + iJdbc.count());
}
}