04-apply

propagation 英 [ˌprɒpə'ɡeɪʃ(ə)n] n. 传播;扩展;宣传;培养

mandatory 英 [ˈmændətəri] adj. 强制性的;强制的;法定的;义务的

Spring框架提供了很多操作模板类:

  1. JdbcTemplate(操作关系型数据)
  2. RedisTemplate(操作nosql数据库)
  3. 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

  1. 事务(Transaction)指一个操作序列,该操作序列是一个不可分割的工作单位。是DB环境中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理
  2. 常用存储引擎InnoDB(MySQL5.5以后默认的存储引擎)和MyISAM,其中InnoDB支持Trx,MyISAM不支持

1. 特性

  • 一个操作序列要成为事务,必须满足ACID特性

1. 原子性(Atomicity)

  1. 原子是自然界最小的颗粒,具有不可再分的特性。事务中的所有操作可以看做一个原子,事务是应用中不可再分的最小逻辑执行体
  2. 要么全部执行,要么全不执行

2. 一致性(Consistency)

  • 事务执行结果必须使DB从一个一致性状态,到另一个一致性状态。当DB中只包含事务成功提交的结果时,BD处于一致性状态。一致性是通过原子性来保证的
  • eg:转账时,只有保证转出和转入的金额一致才能构成事务。也就是说事务发生前和发生后,数据的总额依然匹配

3. 隔离性(Isolation)

  1. 指各个事务执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的
  2. 并发执行的事务之间既不能看到对方的中间状态,也不能相互影响
    • 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实现

  1. 编程式事务(不推荐)
  2. 声明式事务(掌握)。底层为AOP,AOP底层为动态代理
    1. 注解(简单,必会)
    2. XML配置(繁琐,了解)
  3. 事务管理应放在Service层进行处理
  4. 事务管理器接口:PlatformTransactionManager,针对不同框架,提供不同实现类
    • jdbc应用的事务管理类DataSourceTransactionManager
image-20221010215331859

1. @Transactional

  1. 类上,类中所有方法都添加事务控制
  2. 方法上,当前方法增加事务控制
  3. 父类声明的@Transactional会对子类的所有方法进行事务增强。子类可重写父类@Transactional配置
  4. 类名上使用@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
  1. 事务的传播行为,默认为REQUIRED
  2. 多事务方法之间调用,事务如何管理
传播属性描述
REQUIRED加入当前事务;如果没有,新建一个
SUPPORTS加入当前事务;如果没有,非事务运行
MANDATORY加入当前事务;如果没有,抛出异常
REQUIRED_NEW挂起当前事务;新建事务运行
NOT SUPPORTS挂起当前事务,抛异常;如果没有,非事务运行
NEVER当前事务,抛异常;如果没有,非事务运行
NESTED当前事务设置保存点,新建嵌套事务运行。如果没有,新建事务运行
2. isolation
  1. 用于决定如何控制并发用户读写数据的操作
  2. DB允许多用户并发访问,如果多个用户同时开启事务并对同一数据进行读写操作,可能出现脏读、不可重复读、幻读问题
    • 所以MySQL中提供了四种隔离级别来解决并发问题
  3. DEFAULT(默认)
    1. PlatformTransactionManager默认的隔离级别,即使用DB默认的事务隔离级别
    2. 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
image-20221012091805547
@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框架自带通用日志封装,也可以整合自己的日志

  1. Spring移除了LOG4jConfigListener,官方建议使用log4j2
  2. 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());
    }
}