SpringJdbcTemplate
Spring的JdbcTemplate是一个对JDBC的模板封装,它提供了一套JDBC的模板,能让我们写持久层代码时减少多余的代码,简化JDBC代码,使代码看起来更简洁。在介绍Spring的JdbcTemplate使用方法之前我们先来讨论一个问题,以下这是一段常见的往数据库写入数据的JDBC代码:
public int jdbcInsert(Student student) throws SQLException { Connection connection = null; try { connection = dataSource.getConnection(); String sql = "INSERT INTO student(sname,age,sex,address) VALUES (?,?,?,?)"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, student.getName()); preparedStatement.setInt(2, student.getAge()); preparedStatement.setString(3, student.getSex()); preparedStatement.setString(4, student.getAddress()); return preparedStatement.executeUpdate(); } finally { connection.close(); } } public int jdbcUpdate(Student student) throws SQLException { Connection connection = null; try { connection = dataSource.getConnection(); String sql = "UPDATE student SET sname=?,age=?,sex=?,address=?"; PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, student.getName()); preparedStatement.setInt(2, student.getAge()); preparedStatement.setString(3, student.getSex()); preparedStatement.setString(4, student.getAddress()); return preparedStatement.executeUpdate(); } finally { connection.close(); } }
从如上的代码中,可以看到两个方法中基本99%的代码都是重复的,除了sql语句之外,都是重复的代码,重复的代码就是坏味道,会让我们的产生大量的冗余代码,不易于维护和修改,而且写起来还累。
所以Spring提供的JdbcTemplate正是用来解决这个问题的,其实Spring的JDBCTemplate有点像DBUtils,但是有时候还没有DBUitls好用。这里来学习一下使用Spring的JdbcTemplate来玩一下CRUD,毕竟JdbcTemplate在实际开发中一般不会使用,通常都是使用Mybatis、Hibernate等成熟、优秀的数据持久层框架,不过还是得知道Spring有一个这样的jdbc模板类
添加依赖
spring核心包4+2 jdbc模板包2个
<!--spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.9.RELEASE</version> </dependency>
测试
@Test public void test(){ //构建数据源 DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///test"); dataSource.setUsername("root"); dataSource.setPassword("root"); //获取对象 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); //创建表 jdbcTemplate.execute("create table test01(name varchar(10) )"); }
Spring配置数据源
目标:将数据源和jdbcTemplate都交给Spring来管理:
在applicationContext.xml中配置dataSource连接池和jdbcTemplate模版对象。
spring内置数据源
<!--spring内置数据源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class JdbcTempleTest { //注入jdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; @Test public void test(){ this.jdbcTemplate.execute("create table jdbctest05(name varchar(10))"); } }
dbcp数据源
Apache commons-dbcp 需要导入dbcp包和 pool包
添加依赖
<dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency>
<!--dbcp数据源--> <bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource2"/> </bean>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class JdbcTempleTest { //注入jdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; @Test public void test(){ this.jdbcTemplate.execute("create table jdbctest06(name varchar(10))"); } }
C3P0 数据源
<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
<!--c3p0数据源--> <bean id="dataSource3" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql:///test"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> <!--jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource3"/> </bean>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class JdbcTempleTest { //注入jdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; @Test public void test(){ this.jdbcTemplate.execute("create table jdbctest07(name varchar(10))"); } }
Druid数据源
<!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.3</version> </dependency>
<!--druid数据源--> <bean id="dataSource4" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> <!--最大连接数--> <property name="maxActive" value="5"/> <!--最小连接数--> <property name="minIdle" value="1"/> </bean> <!--jdbcTemplate--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource4"/> </bean>
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class JdbcTempleTest { //注入jdbcTemplate @Autowired private JdbcTemplate jdbcTemplate; @Test public void test(){ this.jdbcTemplate.execute("create table jdbctest07(name varchar(10))"); } }
- 相关配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5402RTk-1664592948340)(assets/image-20211217000623540.png)]
外部属性文件的配置
- db.properties
jdbc.driverClass=com.mysql.cj.jdbc.Driver jdbc.jdbcurl=jdbc:mysql:///test?serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=root
- applicationContext.xml
<!--引入外部的文件--> <context:property-placeholder location="classpath:db.properties"/> <!--druid数据源--> <bean id="dataSource4" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.jdbcurl}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!--最大连接数--> <property name="maxActive" value="5"/> <!--最小连接数--> <property name="minIdle" value="1"/> </bean>
基于JdbcTemplate实现CURD
数据库准备
CREATE TABLE `t_book` ( `id` int(11) NOT NULL, `name` varchar(255) DEFAULT NULL, `price` decimal(10,2) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
查询
实体类
@Data public class Book { private Integer id; private String name; private Double price; }
dao层
/** * @Auther: yanqi * @Desc: 继承 JdbcDaoSupport类,父类中已注入JdbcTemplate对象 */ public class BookDao extends JdbcDaoSupport { /** * 查询所有 */ public List<Book> queryAll(){ return super.getJdbcTemplate().query("select * from t_book", new BeanPropertyRowMapper<>(Book.class)); } }
数据源
<!--引入外部的配置文件db.properties--> <context:property-placeholder location="classpath:db.properties"/> <!--Druid连接池--> <bean id="dataSource4" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.jdbcurl}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--dao层--> <bean id="bookDao" class="cn.yanqi.dao.BookDao"> <property name="dataSource" ref="dataSource4"/> </bean>
- db.properties
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcurl=jdbc:mysql:///test jdbc.username=root jdbc.password=root
- 测试
/** * @Auther: yanqi */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class JdbcTemplateTest { @Autowired private BookDao bookDao; @Test public void test2(){ List<Book> books = this.bookDao.queryAll(); for(Book book : books){ System.out.println(book); } } }
实现增加、删除、修改功能
/** * 添加 */ public void addBook(Book book){ String sql = "insert into t_book(id,name,price) values(null,?,?)"; super.getJdbcTemplate().update(sql,book.getId(),book.getName(),book.getPrice()); } /** * 删除 */ public void deleteBook(Integer id){ String sql = "delete from t_book where id = ?"; super.getJdbcTemplate().update(sql,id); } /** * 修改 */ public void updateBook(Book book){ String sql = "update t_book set name = ? where id = ?"; super.getJdbcTemplate().update(sql,book.getName(),book.getId()); }
Spring事务管理
基于_XML的事务管理
编写转账案例,引出事务问题
- 数据库准备
CREATE TABLE `t_account` ( `id` int(11) DEFAULT NULL, `name` varchar(255) DEFAULT NULL, `money` double DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
spring-transaction
- 添加依赖
<dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.4</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>runtime</scope> </dependency> </dependencies>
- dao层
public class AccountDao extends JdbcDaoSupport { public void out(String outName , double money){ String sql = "update t_account set money = money - ? where name = ?"; this.getJdbcTemplate().update(sql,money,outName); } public void in(String inName , double money){ String sql = "update t_account set money = money + ? where name = ?"; this.getJdbcTemplate().update(sql,money,inName); } }
- service层
public class AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void tranfar(String outName , String inName , double money){ this.accountDao.out(outName,money); int a = 1/0; this.accountDao.in(inName,money); } }
- db.properties
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///test jdbc.username=root jdbc.password=root
- applicationContext.xml
<?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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--引入外部的文件--> <context:property-placeholder location="classpath:db.properties"/> <!--druid数据源--> <bean id="dataSource4" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!--最大连接数--> <property name="maxActive" value="5"/> <!--最小连接数--> <property name="minIdle" value="1"/> </bean> <!--dao--> <bean id="accountDao" class="cn.yanqi.dao.AccountDao"> <property name="dataSource" ref="dataSource4"/> </bean> <!--service--> <bean id="accountService" class="cn.yanqi.service.AccountService"> <property name="accountDao" ref="accountDao"/> </bean> </beans>
- 测试
@RunWith(SpringRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void tranfar() { this.accountService.tranfar("jack","rose",100); } }
发现事务管理问题:没有事务的情况下,如果出现异常,则会转账不成功,数据异常。 扩展:如果不配置事务,那么每一个数据库的操作都是单独的一个事务。
加入事务
- 添加tx事务约束
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" 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="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource4"/> </bean> <!--配置事务方法--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- name:事务所管理的方法 isolation: 事务隔离级别,默认值DEFAULT,使用的是数据库的默认隔离级别(mysql默认隔离级别:repeatable read 可重重读) propagation: 事务传播形为,默认值REQUIRED 支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 timeout:事务超时时间(单位是秒),默认值-1,使用的数据库的超时时间 rollback-for: 指哪些异常可以回滚 no-rollback-for: 指哪些异常不回滚,其他都回滚 --> <tx:method name="transactionAccount" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" rollback-for="" no-rollback-for=""/> <!--<tx:method name="update*"/>--> <!--<tx:method name="save*"/>--> </tx:attributes> </tx:advice> <!--配置aop--> <aop:config> <!--切入点--> <aop:pointcut id="txPointcut" expression="bean(*Service)"/> <!--切面--> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config> </beans>
- 测试
/** * @Auther: yanqi */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class AccountServiceTest { @Autowired private AccountService accountService; @Test public void transactionAccount() { this.accountService.transactionAccount("jack","rose",100.0); } }
遇到异常不回滚(相当于没有事务)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kb1d6f3J-1664592948342)(assets/image-20211217202707415.png)]
事务的传播行为PropagationBehavior
什么是事务的传播行为?
一个事务调用了另 一个事务,如果出现错误,两个事务是否属于同一个事务
有什么作用?
事务传播行为用于解决两个被事务管理的方法互相调用问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JlTaNfFY-1664592948344)(assets/image-20211217202839587.png)]
- 事务的传播行为的7种类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1A4nxQU-1664592948345)(assets/image-20211217202857824.png)]
- 主要分为三大类
PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY 支持当前事务, A调用B,如果A事务存在,B和A处于同一个事务 。 事务默认传播行为 REQUIRED。最常用的。 PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER 不会支持原来的事务 ,A调用B, 如果A事务存在, B肯定不会和A处于同一个事务。 常用的事务传播行为:PROPAGATION_REQUIRES_NEW PROPAGATION_NESTED 嵌套事务 ,只对DataSourceTransactionManager有效 ,底层使用JDBC的SavePoint机制,允许在同一个事务设置保存点,回滚保存点
基于_注解声明式事务
导入的依赖包跟xml方式一样
pring-transactional-ann
声明式事务
/** * @Auther: yanqi */ @Service // @Transactional //当前类都会被 事务管理 public class AccountService { @Autowired private AccountDao accountDao; @Transactional //事务管理 public void transactionAccount(String outName, String inName , Double money){ this.accountDao.out(outName,money); int a = 1/0; this.accountDao.in(inName,money); } }
- dao层
/** * @Auther: yanqi * @Desc: 先用jdbcTemplate完成对数据库的操作 */ @Repository public class AccountDao extends JdbcDaoSupport { /** * 随便写一个set方法,注入DataSource,使用父数据源 * @param dataSource */ @Autowired public void setJdbcDaoSupport(DataSource dataSource){ super.setDataSource(dataSource); } /** * 减钱 */ public void out(String name , Double money){ String sql = "update t_account set money = money - ? where name = ?"; super.getJdbcTemplate().update(sql,money, name); } /** * 加钱 */ public void in(String name , Double money){ String sql = "update t_account set money = money + ? where name = ?"; super.getJdbcTemplate().update(sql,money, name); } }
配置注解事务
<!--开启注解--> <context:component-scan base-package="cn.yanqi"/> <!--引入外部的配置文件db.properties--> <context:property-placeholder location="classpath:db.properties"/> <!--Druid连接池--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--事务的平台管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--开启事务的注解--> <tx:annotation-driven transaction-manager="transactionManager"/>
- 配置事务的定义属性信息,在注解中直接配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpZrVRlh-1664592948346)(assets/image-20211217203126239.png)]
- 优化配置
配置注解驱动事务管理的时候,tx:annotation-driven/默认会自动查找并加载名字为transactionManager的事务管理器Bean,因此,当事务管理器的bean的名字为transactionManager 时,可以省略transaction-manager的属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pNV522gW-1664592948347)
(assets/image-20211217203218262.png)] taSource">
- **配置事务的定义属性信息,在注解中直接配置** [外链图片转存中...(img-EpZrVRlh-1664592948346)] - **优化配置** >配置注解驱动事务管理的时候,<tx:annotation-driven/>默认会自动查找并加载名字为transactionManager的事务管理器Bean,因此,当事务管理器的bean的名字为transactionManager 时,可以省略transaction-manager的属性 [外链图片转存中...(img-pNV522gW-1664592948347)]