Spring框架-JdbcTemplate

1.概念和准备

1.1 什么是 JdbcTemplate
  • Spring 框架对JDBC进行封装,使用 JdbcTemplate 方便实现对数据库操作
1.2 准备工作
  1. 引入依赖:

    • `spring-jdbc-xxx.jar``
    • ``spring-orm-5.2.6.xxx.jar`
    • spring-tx-5.2.6.xxx.jar
    • ``druid-xxx.jar`
    • mysql-connector-java-xxx.jar`
  2. 在 spring 配置文件配置数据库连接池:

<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${prop.driverClassName}"></property>
    <property name="url" value="${prop.url}"></property>
    <property name="username" value="${prop.username}"></property>
    <property name="password" value="${prop.password}"></property>
</bean>
  1. 配置 JdbcTemplate 对象,注入 DataSource:
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!--注入 dataSource,因为JdbcTemplate类的有参构造方法中需要参数DataSource,而DataSource是在其父类的一个属性,所以这里是对其父类的一个set注入,不是说直接传入DataSource作为JdbcTemplate类的有参构造方法中的参数-->
    <property name="dataSource" ref="dataSource"></property>
</bean>
  1. 创建 service 类和dao 类,在service注入dao对象以及在dao注入 JdbcTemplate 对象:
<!--注意开启组件扫描-->
<context:component-scan base-package="com.psj"></context:component-scan>
@Service
public class BookService {
    @Autowired
    private BookDao bookDao;
}
@Repository
public class BookDaoImpl implements BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}

2.JdbcTemplate 操作数据库

  1. 对应数据库的表字段创建实体类:
public class Book {
    private String userId;
    private String username;
    private String usstatus;

    public String getUserId() {
        return userId;
    }
    // 剩余的set和get方法
  1. 编写 service 和 dao:
@Service
public class BookService {
@Autowired
    private BookDao bookDao;

    public void addBook(Book book){
        bookDao.add(book);
    }

    // 查询表记录数目
    public int findCount(){
        return bookDao.selectCount();
    }

    // 查询书本
    public Book findOne(String id){
        return bookDao.findBookInfo(id);
    }

    // 查询所有书本集合
    public List<Book> findAll(){
        return bookDao.findAllBook();
    }

    // 批量添加书本
    public void batchAdd(List<Object[]> batchArgs){
        bookDao.batchAddBook(batchArgs);
    }
}
@Repository
public class BookDaoImpl implements BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void add(Book book) {
        String sql = "insert into book values(?,?,?)";
        Object[] args = {book.getUserId(), book.getUsername(), book.getUsstatus()};
        int update = jdbcTemplate.update(sql, args);
        System.out.println(update);  // 表示成功添加的行数
    }

    @Override
    public int selectCount() {
        String sql = "select count(*) from book";
        // 查询返回某个值
        // 第二个参数为返回的类型
        int count = jdbcTemplate.queryForObject(sql, Integer.class);
        return count;
    }

    @Override
    public Book findBookInfo(String id) {
        String sql = "select * from book where user_id = ?";
        // 查询返回某个对象
        // 第二个参数为RowMapper,它是接口,针对返回的不同类型数据,使用这个接口里面完成类的数据封装
        Book book = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Book>(Book.class), id);
        return book;
    }

    @Override
    public List<Book> findAllBook() {
        String sql = "select * from book";
        List<Book> bookList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Book>(Book.class));
        return bookList;

    }

    @Override
    public void batchAddBook(List<Object[]> batchArgs) {
        String sql = "insert into book values(?,?,?)";
        // 将List数组中的每一组元素添加给占位符
        int[] update = jdbcTemplate.batchUpdate(sql, batchArgs);
        System.out.println(update);

    }
}
// 测试
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
BookService bookService = context.getBean("bookService", BookService.class);
List<Object[]> batchArgs = new ArrayList<>();
Object[] o1 = {"3","java","a"};
Object[] o2 = {"4","c++","b"};
Object[] o3 = {"5","MySQL","c"};
batchArgs.add(o1);
batchArgs.add(o2);
batchArgs.add(o3);
bookService.batchAdd(batchArgs);

3.事务操作

3.1 事务概念和特点
  • 事务是数据库操作最基本单元,逻辑上的一组操作(比如A转账给B100元,A要少100元,B同时要多100元)要么都成功,如果有一个失败所有操作都失败
  • 事务四个特性(ACID):
    • 原子性:要么都成功,要么都失败
    • 一致性:操作前和操作后的总量不变
    • 隔离性:多事务操作的时候它们之间不会产生影响
    • 持久性:事务提交后表中数据发生变化
3.2 异常场景
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    public void accountMoney(){
        userDao.reduceMoney(); // psw少100
        int i = 1 / 0;  // 模拟异常
        userDao.addMoney(); // psj多100
    }
}
@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

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

    @Override
    public void reduceMoney() {
        String sql = "update account set money=money-? where username=?";
        jdbcTemplate.update(sql, 100, "psw");  // psw少100
    }
}
// 正常执行userService.accountMoney()后数据库的变化是没有问题的,但是如果代码执行过程中出现异常。但此时出现除数异常,数据库中psw少100但是psj没有多100。需要使用事务进行解决
3.3 事务管理
  • 编程式事务管理:会有很多冗余的代码
public void accountMoney() {
        try {
            // 1.开启事务

            // 2.业务操作
            userDao.reduceMoney(); // psw少100
            int i = 1 / 0;  // 模拟异常
            userDao.addMoney(); // psj多100

            // 3.没有发生异常就提交事务

        } catch (Exception e) {
            // 4.出现异常则事务回滚(回到操作之前的状态)
        }
}
  • 声明式事务管理:底层使用AOP

    • 基于注解:
    // 需要先引入名称空间
    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源-->
            <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    
    @Service
    // 可以加在类上(类中所有方法都添加事务),也可以加在方法上
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
    public class UserService {
        @Autowired
        private UserDao userDao;
    
        public void accountMoney() {
            // 注意把try-catch删除,不删除不会执行回滚操作,直接被catch
            userDao.reduceMoney(); // psw少100
            int i = 1 / 0;  // 模拟异常
            userDao.addMoney(); // psj多100
        }
    }
    // 此时再次执行accountMoney(),数据库中的两个人的money值都不会变化
    
    • 基于XML:
    <!--1.创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源-->
            <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--2.配置通知-->
    <tx:advice id="txadvice">
        <!--配置事务参数-->
        <tx:attributes>
            <!--指定哪种规则的方法上面添加事务-->
            <tx:method name="accountMoney" propagation="REQUIRED"/>
            <!--<tx:method name="account*"/>-->  // 表示为account开头的方法添加事务
        </tx:attributes>
    </tx:advice>
    <!--3.配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.psj.JDBCTemplate.service.UserService.*  	(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
    </aop:config>
    
    • 完全注解:
    @Configuration  // 表示该类是配置类
    @ComponentScan(basePackages = "com.psj.JDBCTemplate")  // 开启组件扫描
    @EnableTransactionManagement  // 开启事务
    public class TxConfig {
        // 创建数据库连接池
        @Bean
        public DruidDataSource getDruidDataSource(){
            DruidDataSource dataSource = new DruidDataSource();
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC");
            dataSource.setUsername("root");
            dataSource.setPassword("xxxx");
            return dataSource;
        }
    
        @Bean
        // 创建JDBCTemplate对象
        public JdbcTemplate getJdbcTemplate(DataSource dataSource){
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            // 注入dataSource:IOC容器中会自动去找DataSource类型的注入对象
            jdbcTemplate.setDataSource(dataSource);
            return jdbcTemplate;
        }
    
        @Bean
        public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
            // 注入dataSource:IOC容器中会自动去找DataSource类型的注入对象
            dataSourceTransactionManager.setDataSource(dataSource);
            return dataSourceTransactionManager;
        }
    }
    
    // 对应上述代码
    <!--开启组件扫描-->
    <context:component-scan base-package="com.psj.JDBCTemplate"></context:component-scan>
    
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClassName}"></property>
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>
    <!-- JdbcTemplate 对象 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入 dataSource,因为JdbcTemplate类中有参构造就是传入该参数-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    

tips:

  • 事务添加到Service层中(理论上可以加到三层中的任意一层)
  • Spring 事务管理是提供一个接口,代表事务管理器,这个接口针对不同的框架提供不同的实现类,如下图中对Hibernate等都有实现类

在这里插入图片描述

  • @Transactional中的参数介绍:

    • propagation:事务传播行为(即多事务方法直接进行调用,则该过程事务是如何进行管理的),比如在已经加了事务注解的add方法中调用没加事务注解的update方法,事务该如何管理(传播行为有7种,以下两种最常见)

      • REQUIRED:默认值。如果add方法有事务,被调用的update方法会使用add方法的事务;如果add方法没有事务的话,update方法就使用自己的事务
      • REQUIRED_NEW:无论add方法是否有事务,update方法都创建新的事务

      在这里插入图片描述

  • ioslation:事务隔离级别。事务的隔离性保证多事务操作(并发操作)之间不会产生影响,不考虑隔离性产生很多问题(三个读的问题)

    • 脏读:一个未提交事务读取到另一个未提交事务的数据(事务未提交就可以回滚)。假如psj开启事务A,psw开启事务B,此时psw将一条数据中的money由100改为200,此时psj读到的数据是200,但是如果psw开启事务回滚,数据又变回原来的100,和psj读到的数据不一致
    • 不可重复读:一个未提交事务读取到另一提交事务修改的数据。假如psj开启事务A,psw开启事务B,此时psw将一条数据中的money由100改为200,并且立刻提交事务。此时psj还没提交事务,但是却读到的数据变为200,正常情况应该要等psj提交完事务数据才会改变(为什么加不可重复读?假设psj原来读到100,可是在psw修改为200并提交事务后就再也读不到100这个值了)
    • 虚读(幻读):一个未提交事务读取到另一提交事务添加的数据,和不可重复读类似,就是psw添加数据并提交事务后,psj能读到该数据(相当于于psj第一查询有10条数据,等第二次查询就变成12条了)

    在这里插入图片描述

  • timeout:超时时间。事务需要在一定时间内进行提交,如果不提交则进行回滚(默认值是 -1)

  • readOnly:是否只读。默认值 false,表示可以查询也可以添加修改删除操作

  • rollbackFor:回滚。设置出现哪些异常时进行事务回滚

  • noRollbackFor:不回滚。设置出现哪些异常时不进行事务回滚

  • 事务方法:对数据库数据进行变化的操作


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值