Spring框架(2-2):Spring5基础 JdbcTemplate/事务管理

x33g5p2x  于2021-12-16 转载在 Spring  
字(18.2k)|赞(0)|评价(0)|浏览(358)

1、JdbcTemplate

1.1、方法:

1.2、举例:

2、事务管理

2.1、注解实现声明式事务管理:

(2)xml实现声明式事务管理:

3、Spring5新特性

3.1. 自带了日志封装

3.2. @Nullable注解

3.3. 支持函数式风格编程

3.4. 支持整合JUnit5

1、JdbcTemplate

  • Spring对JDBC进行封装,使用JdbcTemplate方便对数据库的操作。

  • 引入依赖

  • 在spring配置文件配置数据库连接池

  • 配置jdbcTemplate对象,注入DataSource

  • 创建service类,创建dao类,在dao注入jdbcTemplate对象

1.1、方法:

(1)增删改操作:

int update(String sql, Object... args);

(2)查询:返回某个值

T queryForObject(String sql,Class<T> requiredType);

(3)查询:返回某个对象

T queryForObject(String sql,RowMapper<T> rowMapper,Object ... args);

(4)查询:返回集合

List<T> query(String sql,RowMapper<T> rowMapper,Object... args);

(5)批量增删改:

int[] batchUpdate(String sql,List<Object[]> batchArgs);

1.2、举例:

  • 引入相关jar包
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>
  • 配置数据库连接池;配置JdbcTemplate对象
<context:component-scan base-package="com.oymn"></context:component-scan>

<!--配置数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>
  • 创建Service类和Dao类,在Dao类中注入JdbcTemplate对象
public interface BookDao {

    public void add(Book book);  //添加图书

    public void update(Book book);  //修改图书

    public void delete(int id);  //删除图书

    public int queryCount();   //查询数量

    public Book queryBookById(int id);  //查询某本书

    public List<Book> queryBooks();   //查询所有书

    public void batchAddBook(List<Object[]> books);  //批量添加图书

    public void batchUpdateBook(List<Object[]> books);  //批量修改图书

    public void batchDeleteBook(List<Object[]> args);  //批量删除图书
}
@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {
        String sql = "insert into t_book set name=?,price=?";
        Object[] args = {book.getBookName(),book.getBookPrice()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);
    }

    @Override
    public void update(Book book) {
        String sql = "update t_book set name=?,price=? where id=?";
        Object[] args = {book.getBookName(),book.getBookPrice(),book.getBookId()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);
    }

    @Override
    public void delete(int id) {
        String sql = "delete from t_book where id=?";
        int update = jdbcTemplate.update(sql, id);
        System.out.println(update);
    }

    @Override
    public int queryCount() {
        String sql = "select count(*) from t_book";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        return count;
    }

    @Override
    public Book queryBookById(int id) {
        String sql = "select id bookId,name bookName,price bookPrice from t_book where id=?";
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }

    @Override
    public List<Book> queryBooks() {
        String sql = "select id bookId,name bookName,price bookPrice from t_book";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;
    }

    @Override
    public void batchAddBook(List<Object[]> books) {
        String sql = "insert into t_book set id=?,name=?,price=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, books);
        System.out.println(ints);
    }

    @Override
    public void batchUpdateBook(List<Object[]> books) {
        String sql = "update t_book set name=?,price=? where id=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, books);
        System.out.println(ints);
    }

    @Override
    public void batchDeleteBook(List<Object[]> args) {
        String sql = "delete from t_book where id=?";
        int[] ints = jdbcTemplate.batchUpdate(sql, args);
        System.out.println(ints);
    }
}
@Service
public class BookService {
    @Autowired
    private BookDao bookDao = new BookDaoImpl();
    //添加图书
    public void add(Book book){
        bookDao.add(book);
    }
    //修改图书
    public void update(Book book){
        bookDao.update(book);
    }
    //删除图书
    public void delete(Integer id){
        bookDao.delete(id);
    }
    //查询数量
    public int queryCount(){
        return bookDao.queryCount();
    }
    //查询图书
    public Book queryBookById(Integer id){
        return bookDao.queryBookById(id);
    }
    //查询所有图书
    public List<Book> queryBooks(){
        return bookDao.queryBooks();
    }
    //批量添加图书
    public void batchAddBook(List<Object[]> books){
        bookDao.batchAddBook(books);
    }
    //批量修改图书
    public void batchUpdateBook(List<Object[]> books){
        bookDao.batchUpdateBook(books);
    }
    //批量删除图书
    public void batchDeleteBook(List<Object[]> args){
        bookDao.batchDeleteBook(args);
    }
}

2、事务管理

  • 事务是数据库操作最基本单位,要么都成功,要么都失败。

  • 典型场景:转账

  • 事务四个特性ACID:

  • 原子性

  • 一致性

  • 隔离性

  • 持久性

  • Spring事务管理有两种方式:

  • 编程式事务管理

  • 声明式事务管理

  • 一般使用声明式事务管理,底层使用AOP原理。

  • 声明式事务管理有两种方式:基于xml配置方式 和 基于注解方式,一般使用注解方式。

  • Spring事务管理提供了一个接口,叫事务管理器,该接口针对不同的框架提供不同的实现类。

对于使用JdbcTemplate进行数据库交互,则使用DataSourceTransactionManager实现类,如果整合Hibernate框架则使用HibernateTransactionManager实现类,具体情况具体使用。

案例分析:

public interface UserDao {

    //多钱
    public void addMoney();
    //少钱
    public void reduceMoney();
}
@Repository
public class UserDaoImpl implements UserDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addMoney() {
        String sql = "update user_db set money = money + ? where username = ?";
        jdbcTemplate.update(sql,100,"mary");
    }

    @Override
    public void reduceMoney() {
        String sql = "update user_db set money = money - ? where username = ?";
        jdbcTemplate.update(sql,100,"lucy");
    }
}
@Service
public class UserService {

    //注入dao
    @Autowired
    private UserDao userDao;

    //转账
    public void accountMoney(){
        //lucy少100
        userDao.reduceMoney();
        //mary多100
        userDao.addMoney();

    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

   <context:component-scan base-package="springtransaction"></context:component-scan>

   <!--配置数据库连接池 -->
   <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
      <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
      <property name="username" value="root" />
      <property name="password" value="123456" />
      <property name="driverClassName" value="com.mysql.jdbc.Driver" />
   </bean>

   <!--创建JdbcTemplate对象-->
   <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
      <!--注入数据库连接池-->
      <property name="dataSource" ref="dataSource"></property>
   </bean>


</beans>
public class TestBook {

    @Test
    public void testAccount(){
        ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
        UserService userService = context.getBean("userService", UserService.class);
        userService.accountMoney();

    }
}

上述代码,如果正常执行没有问题,但是如果代码执行过程中,出现异常会有问题。

问题例如:

@Service
public class UserService {

    //注入dao
    @Autowired
    private UserDao userDao;

    //转账
    public void accountMoney(){
        //lucy少100
        userDao.reduceMoney();

        //模拟异常
        int i = 100/0;

        //mary多100
        userDao.addMoney();

    }
}

如何解决:

使用事务,事务过程:

@Service
public class UserService {

    //注入dao
    @Autowired
    private UserDao userDao;

    //转账
    public void accountMoney(){
        try {
            //1、开启事务

            //2、处理逻辑
            //lucy少100
            userDao.reduceMoney();

            //模拟异常
            int i = 100/0;

            //mary多100
            userDao.addMoney();

            //3、没有异常,提交事务
        } catch (Exception e) {
            //4、出现异常,事务回滚
        }

    }
}

1、事务添加到三层结果里Service层(业务逻辑层)

2、在Spring进行事务管理操作

  • 有两种方式:编程式事务管理和声明式事务管理(使用)

3、编程式事务管理:上面代码的1234步骤

4、声明式事务管理有两种方式:基于xml配置方式 和 基于注解方式,一般使用注解方式。

5、在Spring进行声明式事务管理,底层使用AOP原理

6、Spring事务管理API

  • 提供了一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类。

对于使用JdbcTemplate进行数据库交互,则使用DataSourceTransactionManager实现类,如果整合Hibernate框架则使用HibernateTransactionManager实现类,具体情况具体使用。

2.1、注解实现声明式事务管理:

  • 在spring配置文件,配置事务管理器
  • 在spring配置文件,开启事务注解
  • 在service类上面或者service类的方法上面添加事务注解@Transactional
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

在service类上面或者service类的方法上面添加事务注解@Transactional

  • 如果把@Transactional添加在类上面,这个类里面所有方法都添加事务。
  • 如果只是添加在方法上面,则只为这个方法添加事务。

声明式事务管理的参数配置:

在service中配置@Transactional注解,在这个注解里可以配置事务参数:

  1. propagation:事务传播行为,总共有7种

事务方法:对数据库表数据进行变化的操作,比如更新,增加,删除。查询不是。

多事务方法直接进行调用,这个过程中事务是如何进行管理的。例如:

  •  一个事务方法调用另一个无事务方法
  •  一个无事务方法调用另一个事务方法
  •  一个事务方法调用另一个事务方法


**required:**如果add方法本身有事务,调用update方法之后,update使用当前add方法里面事务,如果add方法本身没有事务,调用update方法之后,创建新事物

**required_new:**使用add方法调用update方法,如果add 无论是否有事务,都创建新的事务。

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService {

}
  1. isolation:事务隔离级别

不考虑隔离型会有三个读问题:脏读,不可重复读,虚读(幻读)。

设置隔离级别,解决读问题:

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {

}

  3. timeout:超时时间

  • 事务需要在一定时间内进行提交,超过时间后回滚。
  • 默认值是-1,表示不超时,设置时间以秒为单位。
@Service
@Transactional(timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {

}

  4. readOnly:是否只读

  • 默认值为false,表示可以查询,也可以增删改。
  • 设置为true,只能查询。
@Service
@Transactional(readOnly = true,timeout = -1,propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {

}

  5. rollbackFor:回滚,设置出现哪些异常进行事务回滚。
    6. noRollbackFor:不回滚,设置出现哪些异常不进行事务回滚。

@Service
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public class AccountService {

}

完全注解实现声明式事务管理:

创建配置类,使用配置类代替xml配置文件

@Configuration  //配置类
@ComponentScan(basePackages = "com.oymn.spring5")  //开启组件扫描
@EnableTransactionManagement  //开启事务
public class Config {

    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidDataSource(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");
        return druidDataSource;
    }
    //创建JdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        //到ioc容器中根据类型找到dataSource        
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    //创建事务管理器
    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}
@Service
public class AccountService {

    @Autowired
    private AccountDao accountDao;

    @Transactional
    public void accountMoney(){
        accountDao.add();
        //int i=1/0;   //用来模拟转账失败
        accountDao.reduce();
    }
}

(2)xml实现声明式事务管理:

  • spring配置文件中配置事务管理器
  • spring配置文件中配置通知
  • spring配置文件中配置切入点和切面
<context:component-scan base-package="com.oymn"></context:component-scan>

<!-- 数据库连接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
      destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost:3306/book" />
    <property name="username" value="root" />
    <property name="password" value="000000" />
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>

<!--创建JdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入数据库连接池-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!--配置事务通知-->
<tx:advice id="txadvice">
    <!--配置事务参数-->
    <tx:attributes>
        <tx:method name="accountMoney" propagation="REQUIRED" />
    </tx:attributes>
</tx:advice>

<!--配置切入点和切面-->
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="pt" expression="execution(* com.oymn.spring5.Service.*.*(..))"/>
    <!--配置切面-->
    <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>

3、Spring5新特性

  • 整个Spring5框架的代码基于Java8,运行时兼容JDK9,许多不建议使用的类和方法在代码库中删除。
  • Spring5.0框架 自带了通用的日志框架

3.1. 自带了日志封装

  • Spring5移除了Log4jConfigListener,官方建议使用Log4j2

Spring5整合Log4j2:

第一步:引入jar包

<!--${log4j.version}这是版本信息,可以根据自己的需要填写,我用的是最新的2.11.2-->
		<dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-web</artifactId>
            <version>${log4j.version}</version>
        </dependency>

第二步:创建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>
public class UserLog {

    private static final Logger log = LoggerFactory.getLogger(UserLog.class);

    public static void main(String[] args) {
        log.info("hello log4j2");
        log.warn("hello log4j2");
    }
}

02:33:32.740 [main] INFO springtransaction.UserLog - hello log4j2
02:33:32.747 [main] WARN springtransaction.UserLog - hello log4j2

3.2. @Nullable注解

  • @Nullable注解可以用在方法上,属性上,参数上,表示方法返回值可以为空,属性可以为空,参数可以为空。
@Nullable     //表示方法返回值可以为空
public int getId();

@Nullable     //表示参数可以为空
public void setId(@Nullable int Id);

@Nullable     //表示属性可以为空
public int id;

3.3. 支持函数式风格编程

这是因为java8新增了lamda表达式

@Test
public void test() {
    //1 创建 GenericApplicationContext 对象
        GenericApplicationContext context = new GenericApplicationContext();
        //2 调用 context 的方法对象注册
        context.refresh();
        context.registerBean("user1", User.class,() -> new User());
        //3 获取在 spring 注册的对象
        User user = (User)context.getBean("user1");
        System.out.println(user);//aopanno.User@3d0f8e03

        //3 获取在 spring 注册的对象
        //使用全路径
        context.registerBean( User.class,() -> new User());
        User user1 = (User)context.getBean("com.atguigu.spring5.test.User");
        System.out.println(user1);//aopanno.User@3d0f8e03
    
}

3.4. 支持整合JUnit5

(1)整合JUnit4:

第一步:引入jar包

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.8.RELEASE</version>
        </dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

第二步:创建测试类,使用注解方式完成

@RunWith(SpringJUnit4ClassRunner.class) //单元测试框架版本
@ContextConfiguration("classpath:bean4.xml") //加载配置文件
public class JUnitTest {

    @Autowired
    public User user;

    @Test
    public void test(){
        System.out.println(user);
    }
}

其中

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:bean1.xml")
相当于
ApplicationContext context=new AnnotationConfigApplicationContext(Config.class);

bean4.xml:

<context:component-scan base-package="com.oymn"></context:component-scan>

通过使用@ContextConfiguration注解,测试方法中就不用每次都通过context来获取对象了,比较方便。

ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
BookService bookService = context.getBean("bookService",BookService.class);

(2)整合JUnit5:

<dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter</artifactId>
                <version>5.6.2</version>
                <scope>test</scope>
            </dependency>

相关文章