Day-four
1、Spring JdbcTemlate基本使用
①导入spring-jdbc和spring-tx 【前者封装着Jdbc的模板对象,后者是事务控制的对象】
②创建数据库和实体类
③创建JdbcTemlate对象
④执行数据操作
导入spring-jdbc和spring-tx
<!--c3p0数据库驱动包-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--MySQL驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
<!--spring-tx-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--Junit基本包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
测试类
//测试JdbcTemplate开发步骤
public class JdbcTemplateTest {
@Test
public void test01() throws Exception {
//1、创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
//2、设置数据库信息
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
//3、创建JDBC模板对象:JdbcTemplate
JdbcTemplate template = new JdbcTemplate();
//4、设置数据源对象,告诉数据库在哪(内部会获取数据库连接对象)
template.setDataSource(dataSource);
//5、执行SQL
int i = template.update("insert into account values(?,?)","张三",5000);
System.out.println(i);//影响行数:1
}
}
1.4 Spirng产生JdbcTemplate对象
上述使用JdbcTemlate是比较原始的。
分析发现:
创建JdbcTemplate对象依赖于DataSource对象,而DataSource对象依赖数据库信息,也就是说,我们需要创建2个对象,还要给数据库连接对象设置依赖的属性。
再分析发现:
它们所依赖的,都可以使用setXXX方法来设置属性,所以我们可以把这2个对象创建的控制权交给Spring,在设置Bean的时候,分别使用set方式来进行依赖注入。
下面我们来看看具体怎么实现:
applicationContext.xml
<!--c3p0数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemlate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
测试类
@Test
//测试Spring产生jdbcTemplate对象
public void test02() throws Exception {
//1、加载配置文件,创建Spring容器对象
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、获取JdbcTemplate对象
JdbcTemplate template = app.getBean(JdbcTemplate.class);
//3、执行SQL
int row = template.update("insert into account value (?,?)", "刘德华", 9000);
//4、打印影响行数
System.out.println(row);
}
1.5 Spirng产生JdbcTemplate对象之抽取数据
我们之前也有学过,为了程序代码更加解耦,我们可以把需要设置数据库信息抽取到1个配置文件中,到时候再动态读取。
下面就简单展示一下代码:
(跟上一步的代码一模一样,就多了个jdbc.properties)
jdbc.properties
在resource文件下。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.user=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: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">
<!--引入jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--c3p0数据源对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--jdbc模板对象-->
<bean id="jdbcTemlate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
【记得:】到时候这个applicationContext.xml都是这样一套配置JDBC的。
1.5 Spirng产生JdbcTemplate对象之更新和修改SQL
其实,步骤跟上边一样的。只是这里使用Spring集成Junit测试,记得配置pom.xml导包(Spring集成Junit包)
Spring集成Junit测试类:
@RunWith(SpringJUnit4ClassRunner.class) //叫Spring内核帮我运行
@ContextConfiguration("classpath:applicationContext.xml")//告诉Spring测试哪个配置文件的Bean(从此获取对象)
public class JdbcTemplateCRUDTest {
//需要什么对象就注入什么对象(容器有的对象)
@Autowired
private JdbcTemplate template;
@Test
public void testUpdate(){
int row = template.update("update account set money = ? where name = ?",100000,"刘德华");
System.out.println(row);//打印影响行数
}
}
1.5 Spirng产生JdbcTemplate对象之查询SQL
这里的步骤跟上边一样,只是演示一下使用Spring如何使用JdbcTemplate的CRUD。其实跟以前学得差不多。
这里着重介绍以下几点:
- BeanPropertyRowMapper《T》(T.class)
他是MapBean Property Row Mappe 翻译中文就是:“Bean属性与行的映射”,它是RowMapper(行映射)的实现类。
- Bean属性=实体类的setxxx方法中的xxx;
- 行=查询数据库得到的行数(查询结果集)。
- 映射:是指,我们查询数据库结果会1行1行地显示,每一行的列名都会去寻找JavaBean的属性进行映射,映射成功就进行数据封装,而至少要有1个列名和1个Bean属性产生映射并封装数据到Bean,如果1个都没有就会报异常。
这个“T”代表什么?
”T“代表你想要封装的对象。
- queryForObejct
query是查询的意思。
如果只是使用简单的select就返回1个对象,无论能查出几行。
如果是查询聚合函数,那么返回你想要封装的包装类
- query
query是查询的意思。
如果查询出1行,就返回1个Bean对象
如果查询出多行,就返回Bean对象的List集合
Account实体类:
/**
* 实体类:
* 用于封装数据库数据
*/
public class Account {
private String name;
private double monet;
//get/set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMonet() {
return monet;
}
public void setMonet(double monet) {
this.monet = monet;
}
//toString方法
@Override
public String toString() {
return "Account{" +
"name='" + name + '\'' +
", monet=" + monet +
'}';
}
}
Spring集成Junit测试类
@RunWith(SpringJUnit4ClassRunner.class) //叫Spring内核帮我运行
@ContextConfiguration("classpath:applicationContext.xml")//告诉Spring测试哪个配置文件的Bean(从此获取对象)
public class JdbcTemplateCRUDTest {
//需要什么对象就注入什么对象(容器有的对象)
@Autowired
private JdbcTemplate template;
//总记录数
@Test
public void testQueryCount() {
String sql ="select count(*) from account";
Long sum = template.queryForObject(sql, Long.class);
System.out.println(sum);
}
//查询1个对象
@Test
public void testQueryForObject() {
String sql = "select * from account where name =?";
template.queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class),"刘德华");
}
//查询多个对象
@Test
public void testQueryAll() {
String sql = "select * from account";
List<Account> list = template.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
}
小总结
2、事务控制
a、编程式事务控制
1.1 PlatformTransactionManage:事务管理器
- PlatformTransactionManager(事务管理器):它只是Spring提供管理事务的操作方法的接口。对于不同dao层技术,就有不同的实现类。
- 事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition类 ,这个类就定义了一些基本的事务属性。
1.2 TransactractionDefinition:事务定义对象
getxxx方法,xxx是该类的属性,这个属性在该类定义成)常量。(funal+成员变量
什么是事务的基本属性?
- 事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。
事务属性包含了5个方面:
1、隔离级别
2、传播行为
3、是否超时
4、是否只读
5、回滚规则- 这些属性描述了这个事务具体是什么样的:它是什么隔离级别啊?具有什么传播行为呢?进行事务的操作的有效时间是多少?这个事务是否为只读(只查询)?
TransactractionDefinition:事务定义对象
定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等的常量。
1.2.1 事务传播行为:
业务场景:
A调用B,B根据A有没有事务来做出行为。
使用这个业务场景解释一下某个事务传播行为的定义:
required:A调用B,B看A有没有设置事务管理,如果没有,B就创建新的事务管理,如果有就一起使用这个事务。
1.3 TransactionStatus:描述事务运动状态对象
事务管理器 + 事务定义 = 运动状态
这个运动状态不是我们主动去设置的,它是我们执行事务管理的被动提示。
所以,我们在设置Spring配置时,设置【事务管理器 + 事务定义】就好了。
b、XML声明式事务控制
2.1 什么是声明式事务控制
就是在XML配置文件声明事务管理,让Spirng帮我们去进行事务管理,不需要自己动手写代码。这样子就将事务管理和逻辑代码2个部分进行了分开。
Spring声明式事务控制底层就是AOP:切点就是某个方法,通知就是事务管理
2.2 环境搭建【转账案例】
①创建数据库
②pom.xml导入基本包
<dependencies>
<!--Spring基本包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--Mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<!--c3p0驱动包-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!--Spring'S jdbc 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--Spring集成单元测试-->
<dependency>
<groupId></groupId>
<artifactId></artifactId>
</dependency>
</dependencies>
③appliztionContext.xml配置Bean
<?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"
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">
<!--引入jdbc.properties文件-->
<Context:property-placeholder location="classpath:jdbc.properties"/>
<!--c3p0数据源Bean-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--JdbcTemlate:Spring's JDBC模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--AccountDaoImpl-->
<bean id="accountDao" class="com.itheima.dao.Ipml.AccountDaoIpml">
<property name="template" ref="jdbcTemplate"/>
</bean>
<!--AccountServiceImpl:目标对象-->
<bean id="accountService" class="com.itheima.service.Ipml.AccountServiceIpml">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
④创建dao层、service层、controller层
AccountDaoIpml:
public class AccountDaoIpml implements AccountDao {
private JdbcTemplate template; //定义依赖为成员变量
public void setTemplate(JdbcTemplate template) { //set方法注入依赖对象
this.template = template;
}
//转账方法
public void out(String outMan, double money) {//谁转账,转多少
String sql = "update account set money=money-? where name=?";
template.update(sql, money, outMan);
}
//收账方法
public void in(String inMan, double money) {//谁转账,转多少
String sql = "update account set money=money-? where name=?";
template.update(sql, money, inMan);
}
}
AccountServiceIpml:
public class AccountServiceIpml implements AccountService {
private AccountDao dao;//讲依赖对象作为成员变量
public void setAccountDao(AccountDao dao) {//set方法注入依赖
this.dao = dao;
}
//转账操作
public void transfer(String outMan, String inMan, double money) {//谁转账,谁收账,交易多少钱
dao.out(outMan,money);
int i = 1/0; //手动发生异常
dao.in(inMan,money);
}
}
⑤controller层测试
AccountController:
public class AccountController {
public static void main(String[] args) {
//1、加载配置文件,创建Spring容器
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
//2、获取对象
AccountService service = (AccountService) app.getBean("accountService");
//3、执行方法
service.transfer("吴彦祖","陈冠希",1000);
}
}
MySQL中每一条SQL都是一个事务,当多个独立的事务同时操作一批数据,如果没有被事务管理着就可能会出现一些问题。
为了解决这些问题我们以前是这样手动地添加的:
但是,这只是对于1个方法的设置,如果我们需要设置很多方法呢?而且以后设置了还得修改,是不是这样很麻烦?
所以,我们得利用AOP思想,将事务管理当成增强并且抽取出来,被管理的方法作为切点,在XML配置文件中再将其结合(织入)。
不是很重要的提示:
- Spring已经帮我们封装好了事务管理的代码,我们只要给对于的方法配置好就行了。
- 一般增强的方法都是在业务逻辑层,而此案列中业务逻辑层转出和转入钱的方法就要被管理事务。也就是说,【目标对象】就是业务逻辑层Service
2.3 Spring事务控制-基于xml(代码结合2.2)
①pom.xml导包
<!--AOP框架:aspectj的织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
<!---事务管理的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
②配置ApplicationContext.xml
记得导命名空间:context(引入)、aop(aop织入)、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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--引入jdbc.properties文件-->
<Context:property-placeholder location="classpath:jdbc.properties"/>
<!--c3p0数据源Bean-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/> //set注入依赖
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--JdbcTemlate:Spring's JDBC模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/> //set注入依赖
</bean>
<!--AccountDaoImpl-->
<bean id="accountDao" class="com.itheima.dao.Ipml.AccountDaoIpml">
<property name="template" ref="jdbcTemplate"/> //set注入依赖
</bean>
<!--AccountServiceImpl:目标对象(内部的方法就是切点)-->
<bean id="accountService" class="com.itheima.service.Ipml.AccountServiceIpml">
<property name="accountDao" ref="accountDao"/> //set注入依赖
</bean>
<!--配置事务管理器对象:指定使用哪一种事务管理技术-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/> //set注入依赖:name=属性名
</bean>
<!--配置通知:事务的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">//transaction-manager:给通知指定使用哪一种的事务管理技术
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置织入:告诉Spring哪些方法需要增强-->
<aop:config>
<!--抽取切点-->
<aop:pointcut id="txPointcut" expression="execution(* com.itheima.service.Ipml.*(..))"/>
<!--声明切面:告诉Spring哪个是切面类-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
advisor也是切面的意思,只是advisor是特定声明事务的,而aspect是用来声明普通类的切面的。【要区分2者配置的结构】
- 为什么要设置【配置事务管理器对象】?
实现事务管理的实现类是后很多种的,我们要指定一种
- 为什么配置事务管理器对象时要注入数据源?
首先,这个事务管理器对象是封装了我们操作事务的方法,而操作事务的方法本质上是通过连接对象调用的,而连接对象是通过数据源对象获取的。
③测试运行
④结果
如果转账中途报错,回滚事务;如果没出现异常,交易成功。
详解XML
知识要点
这3步基本是写死的了。
c、基于注解的声明式事务控制
此处在XML的代码中进行修改。
我们一般开发的原则是,自己编写的类使用注解,第三方的类使用XML配置。
下面展示代码:
1、applicationContext.xml
这里我们移除了dao和service,增加了事务注解驱动和注解组件扫描。
<!--引入jdbc.properties文件-->
<Context:property-placeholder location="classpath:jdbc.properties"/>
<!--c3p0数据源Bean-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--JdbcTemlate:Spring's JDBC模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/><!--注入依赖-->
</bean>
<!--注解组件扫描-->
<Context:component-scan base-package="com.itheima"/>
<!--事务注解驱动:没加就没有事务增强功能-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
2、AccountDaoIpml
@Repository("accountDao")//告诉Spring帮我实例化Bean
public class AccountDaoIpml implements AccountDao {
@Autowired //自动注入依赖
private JdbcTemplate template; //定义依赖为成员变量
//转账方法
public void out(String outMan, double money) {//谁转账,转多少
String sql = "update account set money=money-? where name=?";
template.update(sql, money, outMan);
}
//收账方法
public void in(String inMan, double money) {//谁转账,转多少
String sql = "update account set money=money-? where name=?";
template.update(sql, money, inMan);
}
}
3、AccountServiceIpml
@Service("accountService") //告诉Spring帮我实例化Bean
//@Transactional 代表配置此类所有的方法设置为事务通知(参数随便)
public class AccountServiceIpml implements AccountService {
@Autowired //自动注入对象
private AccountDao dao;//讲依赖对象作为成员变量
}
// @Transactional //配置事务通知。(参数为定义的事务基本属性)
// @Transactional(isolation = Isolation.READ_COMMITTED)
@Transactional //代表每个方法都有独立的事务管理
public void xxx(){
}
//转账操作 ↓(配置通知的基本属性:可以有参数、无参)
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.MANDATORY)
public void transfer(String outMan, String inMan, double money) {//谁转账,谁收账,交易多少钱
dao.out(outMan,money);
dao.in(inMan,money);
}