1.概念和准备
1.1 什么是 JdbcTemplate
- Spring 框架对
JDBC
进行封装,使用JdbcTemplate
方便实现对数据库操作
1.2 准备工作
引入依赖:
- `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`
在 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>
- 配置 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>
- 创建 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 操作数据库
- 对应数据库的表字段创建实体类:
public class Book { private String userId; private String username; private String usstatus; public String getUserId() { return userId; } // 剩余的set和get方法
- 编写 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
:不回滚。设置出现哪些异常时不进行事务回滚事务方法:对数据库数据进行变化的操作