本文是Spring的学习下篇,主要讲JDBCTemplete、事务以及Spring相关注解总结。
Spring的IOC和AOP的知识,请见上篇。
Spring——Spring学习教程(详细)(上篇)——IOC、AOP
7、JDBCTemplete
Spring给我们提供的JDBC的工具,可以看作一个简易的ORM(对象关系映射)框架。
持久层的操作发展:JDBC——JDBCTemlete——Hibernate——MyBatis——MyBatis-plus
7.0 传统JDBC操作示例
7.0.1 传统JDBC一般步骤
传统JDBC操作一般步骤:
(1)使用JDBC编程需要连接数据库,注册驱动和数据库信息。
(2)操作Connection,打开 Statement对象。(PreparedStatement是Statement的一个子类)
(3)通过Statement执行SQL,返回结果到ResultSet对象。
(4)使用ResultSet 读取数据,然后通过代码转化为具体的POJO对象。
(5)关闭数据库的相关资源。
7.0.2 传统JDBC小案例
首先:数据库需要建表,我用的是oracle数据库,建立了一个名字为vpm_product的表,还有四个字段,信息可以在下面的Product类中看出。
1、建立Product类用来接收查询结果
package com.test01.jdbc;
/**
* @author zll
* @version 1.0
* @date 2020/7/31 16:48
*/
public class Product {
private int id;
private String name;
private String code;
private double price;
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", code='" + code + '\'' +
", price=" + price +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
2、JDBC应用类
package com.test01.jdbc;
import java.sql.*;
/**
* @author zll
* @version 1.0
* @date 2020/7/31 16:38
*/
public class JdbcExample {
public static void main(String[] args) {
Product pro = new JdbcExample().getProduct(6);//查询id为6的产品的信息
System.out.println(pro);//打印输出查询出来的数据
}
public Product getProduct(int id) {
Connection connection = getConnection();//得到该数据库的连接
PreparedStatement ps = null;//声明一个null的预处理的Statement
ResultSet rs = null;//声明一个结果集,用来存放SQL的查询后的结果
try {
ps = connection.prepareStatement("select * from VPM_TEST t where t.id=?");//对查询的User表的SQL进行预处理编译
ps.setInt(1, id);//把参数Id设值到数据的条件中
rs = ps.executeQuery();//执行查询语句。把结果返回到ResultSet结果集中
while (rs.next()) {//遍历从结果集中取数
int product_id = rs.getInt("id");//取出Statement的用户id
String name = rs.getString("name");//取出Statement的用户名
String code = rs.getString("code");//取出Statement的性别
double price = rs.getDouble("price");//取出Statement的用户地址
Product product = new Product();//创建一个User类的实体对象POJO
product.setId(product_id);//存放在user对象中
product.setName(name);
product.setCode(code);
product.setPrice(price);
return product;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
this.close(rs, ps, connection);
}
return null;
}
//创建一个数据库的连接
private static Connection getConnection() {
Connection connection = null;
try {
Class.forName("oracle.jdbc.OracleDriver");//加载用户驱动
String url = "jdbc:oracle:thin:@10.152.58.1:1521/tkitppm";//连接数据库的地址
String user = "itppm";//数据库的用户名
String password = "itppm2019";//数据库的密码
connection = DriverManager.getConnection(url, user, password);//得到一个数据库的连接
} catch (ClassNotFoundException e) {
// TODO 自动生成的 catch 块
System.out.println(JdbcExample.class.getName() + "数据库驱动包未找到!");
return null;
} catch (SQLException e) {
// TODO 自动生成的 catch 块
System.out.println(JdbcExample.class.getName() + "SQL语句有问题,无法查询成功!");
return null;
}
return connection;//返回该连接
}
//判断数据库是否关闭
/***
*
* @param rs 查看结果集是滞关闭
* @param stmt 预处理SQL是否关闭
* @param conn 数据库连接是否关闭
*/
private void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
System.out.println(JdbcExample.class.getName() + "ResultSet 关闭失败!");
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
System.out.println(JdbcExample.class.getName() + "Statement 关闭失败!");
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
System.out.println(JdbcExample.class.getName() + "Connection 关闭失败!");
}
}
}
运行结果:
7.0.3 传统JDBC的缺点
可见,传统JDBC的操作有以下的缺点:
1、 数据库连接方面。
数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能。使用数据库链接池可解决此问题。
2、 sql在代码中,不易维护。
Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
3、 随着sql变复杂,向sql传参麻烦。
向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
所以之后出现了数据库连接池以及一些优秀的ORM框架。
7.1 概述
为了使JDBC更加易于使用,Spring在JDBC API上定义了一个抽象层,以此建立一个JDBC存取框架。
JDBCTemplete使Spring JDBC框架的核心,为不同类型的JDBC操作提供模板方法,通过这种方式,在保持灵活性的前提下,将数据库存取的工作量降到最低。JDBCTemplete可以看作是一个小型的轻量级持久层框架。
7.2 Spring配置
其实在上篇中有介绍如何配置数据库连接。这里再介绍一遍。
1、可以先建立数据库配置文件db.properties。
# k=v
# 数据库配置
jdbc.driver=oracle.jdbc.OracleDriver
jdbc.url=jdbc:oracle:thin:@****
jdbc.username=***
jdbc.password=***
2、建立jdbc.xml文件配置数据库源以及JDBCTemplate
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 加载资源文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="db.properties"></property>
</bean>
<!--创建数据源-->
<bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--通过数据源配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"></property>
</bean>
</beans>
7.3 JDBCTemplate实现dao
还是继续用之前的Product类,然后编写测试类使用JDBCTemplate。
/**
* @author zll
* @version 1.0
* @date 2020/8/3 9:48
*/
public class TestJdbcTemplate {
ApplicationContext ac = new ClassPathXmlApplicationContext("jdbc.xml");
JdbcTemplate jdbcTemplate = ac.getBean("jdbcTemplate", JdbcTemplate.class);
/**
* 测试update方法
*/
@Test
public void testUpdate() {
//JdbcTemplate中的update可以实现增加、修改、删除。在删除、修改时也可以用in()实现批量删除、修改。
jdbcTemplate.update("insert into vpm_test values (11,'小六','007aad',32.9)");
String sql = "insert into vpm_test values (12,?,?,?)";
jdbcTemplate.update(sql, "六六", "332d", 23.4);
}
/**
* 测试batchUpdate方法
*/
@Test
public void testBatchUpdate() {
//JdbcTemplate中的BatchUpdate可以实现批量的增加、修改、删除
String sql = "insert into vpm_test values(?,?,?,?)";
List list = new ArrayList<Object>();
list.add(new Object[]{13, "小王", "jidff4", 33.6});
list.add(new Object[]{14, "小王", "543dj4", 8.6});
list.add(new Object[]{15, "小王", "gfj4", 94.6});
jdbcTemplate.batchUpdate(sql, list);
}
/**
* 测试queryForObject方法,查询单个值或者单条数据
*/
@Test
public void testQueryForObject() {
// jdbcTemplate.queryForObject(sql,requiredType);用来获取单个的值
// jdbcTemplate.queryForObject(sql,rowMapper);用来获取单条数据
String sql = "select id,name,code,price from vpm_test where id=?";
Object[] args = {7};
RowMapper<Product> rowMapper = new BeanPropertyRowMapper<Product>(Product.class);//将列名(字段名或者字段的别名)与属性名进行映射
Product product = jdbcTemplate.queryForObject(sql, args, rowMapper);
System.out.println(product);
String sql01 = "select count(*) from vpm_test ";
Integer count = jdbcTemplate.queryForObject(sql01, Integer.class);
System.out.println(count);
}
/**
* 测试query方法,查询多条数据
*/
@Test
public void testQuery() {
String sql = "select id,name,code,price from vpm_test";
RowMapper<Product> rowMapper = new BeanPropertyRowMapper<Product>(Product.class);
List<Product> list = jdbcTemplate.query(sql, rowMapper);
for (Product product : list) {
System.out.println(product);
}
}
}
8、声明式事务管理
事务管理是AOP的简单应用,也是AOP最重要的应用。AOP最经典的应用:日志管理和事务管理。
8.1 事务概述
1、在JAVAEE开发的企业级领域为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中不可缺少的技术。
2、事务就是由一组逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要不都执行,要不都不执行。
3、事务的四大特征:ACID。
原子性:整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。
一致性:在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏,如果失败了就回滚。
隔离性:两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时中间某一时刻的数据。
持久性:在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
有关事务的特性的具体解释可见链接:
8.2 Spring事务管理
8.2.1 编程式事务管理
1、使用原生的jDBC API进行事务管理。
(1)获取数据库的Connection对象
(2)取消事务的自动提交
(3)执行操作
(4)正常完成操作时手动提交事务
(5)执行失败时回滚事务
(6)关闭相关资源
2、评价
使用原生JDBC API实现事务管理是所有事务管理方式的基石,同时也是典型的编程式事务管理。编程式事务管理需要把事务管理的代码嵌入到业务方法之中来控制事务的提交和回滚。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都是用相同模式的代码进行事务管理就造成了代码的冗余。
8.2.2 声明式事务管理
大多数情况下声明式事务管理要比编程式事务管理更好,它将事务管理嗲吗从业务代码中抽离出来,以声明的方式实现事务管理。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方式模块化,进而借助Spring AOP框架实现声明式事务管理。
Spring在不同的事务管理API上定义了一个抽象层,通过配置的方法使其生效,从而让开发人员不必了解事务管理API的底层实现的细节,就可以使用Spring的事务管理机制。
Spring支持编程式事务开发,也支持声明式的事务开发。
8.2.3 Spring提供的事务管理器
8.2.4 事务管理器的主要实现
1、Spring配置文件配置事务管理。
<!--通过数据源配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启注解驱动,即对事务相关的注解进行扫描,解析含义并执行功能-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
2、需要导入的一些依赖
<!-- spring aop支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!--AspectJ-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.bundles</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8_2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<!--cglib-->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
8.3 @Transactional 注解
/**
* @Transactional: 对方法中所有的操作作为一个事务进行管理
* 在方法上使用,只对方法有效果;在类上使用,对类中所有的方法都有效果
* @Transactional中可以设置的属性:
* propagation:事务的传播行为
*
* isolation:事务的隔离级别
*
* timeout:指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。单位:秒。
*
* readonly:只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。默认为读写事务。
*
* rollbackfor||norollbackfor:(不会)导致事务回滚的异常类数组
*
* rollbackforClassName||norollbackforClassName:(不会)导致事务回滚的异常类名字数组
*
*/
@Transactional()
public void addUser() {
userDao.addUser();
}
8.3.1 事务的传播行为(propagation)
@Transactional(propagation = Propagation.REQUIRED)
什么叫事务的传播行为?
A方法和B方法都有事务,当A在调用B时,会将A中的事务传播给B方法,B方法对于事务的处理方式就是事务的传播行为。
Spring有7种事务传播行为。Spring事务本质上是管理数据库事务;数据库事务本质上是管理数据库锁。
(1)REQUIRED:(必需)(常用)
如果当前存在事务,则加入该事务;(发生异常同时回滚)
如果当前没有事务,则创建一个新的事务。
(2)SUPPORTS:(支持)
如果当前存在事务,则加入该事务;
如果当前没有事务,则以非事务的方式继续运行。
(3)MANDATORY:(义务)
如果当前存在事务,则加入该事务;
如果当前没有事务,则抛出异常。
(4)REQUIRES_NEW:(需要——新)(常用)
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
(5)NOT_SUPPORTED:(不支持)
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
(6)NEVER:(从不)
以非事务方式运行,如果当前存在事务,则抛出异常。
(7)NESTED:(嵌套)
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
8.3.2 事务的隔离机制(isolation)
在并发的情况下操作数据的一种规定。
@Transactional(propagation = Propagation.REQUIRED,timeout = 3,isolation = Isolation.READ_UNCOMMITTED)
事务并发可能造成的问题?
(1)脏读
即读到了另外一个事务没有提交的数据。例如A、B两个事务同时进行,A对数据进行了更新,B读取到了更新的数据,但是A遇到问题回滚,B读到了无效的“脏”数据。
(2)不可重复读
指的是同一个事务前后两次读取到的数据,发现两次数据不一样,可能是因为两次读取数据中间,有其他事务更新修改了数据,导致两次读取结果不同。
(3)虚读
指的是同一个事务前后两次读取到的数据,发现两次数据的量不一样,可能是因为两次读取数据中间,有其他事务新增或者删除了数据,导致两次读取结果数据量不同。
数据库的事务隔离机制?
为了平衡“隔离”与“并发”的矛盾,可以根据需求选择不同的事务隔离级
(1)Read uncommitted (读未提交):最低级别,以上问题均无法解决。
(2)Read committed (读已提交):读已提交,可避免脏读情况发生。
(3)Repeatable Read(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读(虚读)问题。是MYSQL默认的数据库事务隔离级别。
(4)Serializable (串行化):最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生。相当于单线程,性能低、消耗大。
8.3.3 事务的超时操作(timeout)
timeout属性,表示在事务强制回滚前最多可以执行(等待)的时间。
@Transactional(propagation = Propagation.REQUIRED,timeout = 3)
说明该事务执行超过3秒就强制自动回滚。
8.3.4 事务的只读
指定当前事务中的一系列操作是否为只读。如果设置为只读,那么MYSQL在请求访问数据的时候就会不加锁,提高性能。
@Transactional(propagation = Propagation.REQUIRED, timeout = 3, isolation = Isolation.READ_UNCOMMITTED,
readOnly = true)
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。默认为读写事务。
8.3.5 事务回滚的条件
* rollbackfor||norollbackfor:(不会)导致事务回滚的异常类数组
*
* rollbackforClassName||norollbackforClassName:(不会)导致事务回滚的异常类名字数组
@Transactional(propagation = Propagation.REQUIRED, timeout = 3, isolation = Isolation.READ_UNCOMMITTED,
rollbackFor = {NullPointerException.class, ClassNotFoundException.class, FileNotFoundException.class})
遇到列出的异常,不会回滚,继续执行。
8.4 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"
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-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--context:component-scan :扫描包-->
<context:component-scan base-package="com.test02.usermodule" use-default-filters="true"></context:component-scan>
<!-- 加载资源文件 -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="db.properties"></property>
</bean>
<!--创建数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--通过数据源配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知,被事务管理器管理-->
<tx:advice id="tx" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<!--在设置好的切入点表达式下,再次进行事务设置-->
<tx:method name="testBefore" isolation="READ_COMMITTED" read-only="true"/>
<!--只有add开头的方法才会被事务处理-->
<tx:method name="add*"></tx:method>
<tx:method name="select*" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置切入点表达式.expression越精确越好,更快的扫描到-->
<aop:config>
<aop:pointcut id="cut" expression="execution(* com.test02.aopxml.*.* (..))"></aop:pointcut>
<aop:advisor advice-ref="tx" pointcut-ref="cut"></aop:advisor>
</aop:config>
<!--XML配置事务管理,不用开启注解驱动-->
<!--<!–开启注解驱动,即对事务相关的注解进行扫描,解析含义并执行功能–>-->
<!--<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>-->
</beans>
9、Spring相关注解
9.1 组件类注解
@Controller、@Service、 @Repository、@Component这四个注解作用类似,都是将标注的bean类交由Spring的IOC容器进行管理。在Spring的xml文件中配置这些Spring bean的扫描路径。
@Controller:
被@Controller修饰的类是一个控制器组件类,对应框架的控制层
@Service:
被@Service标注的类是一个业务逻辑组件类,对应框架的业务层
@Repository:
被@Repository标注的类是一个DAO组件类,对应框架的持久层
@Component:
被该注解所修饰的类是一个普通的spring bean类,该注解可以替代@Controller、@Service、@Repository.在不确定具体的bean的时候,可以选择使用@Component注解,但是一般不建议使用。
9.2 bean装配类注解
@Autowired:
Spring进行自动装配,自动为非字面量属性赋值,默认优先通过byType方式进行装配。
@Resource:
不是 Spring 的注解。是java自己提供了@Resource注解,他的作用和@Autowired是一样的,不同点就是@Autowired是先默认byType而@Resource是先默认byName方式装配。
@Qualifier:
@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用。在@Autowired 自动装配的时候,通过@Qualifier制定具体的bean的名称。
@Primary:
自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常。
9.3 AOP相关注解
Spring支持AspectJ的注解式切面编程。
@Aspect
标注当前类为切面。
@Order
定义切面的优先级(正整数,默认值为int的最大值)。如果有多个切面,数字越小的优先级越高。
@PointCut
声明切入点。
@Before
将方法指定为前置通知,作用于方法执行之前。
@After
后置通知,作用于方法执行后,是在finally语句块里的,不管有无异常一定会被执行。
@AfterReturning
返回通知:有异常的话不执行,作用于方法返回之后。
@AfterThrowing
将方法标注为异常通知,作用于当方法抛出异常时。
@Around
相当于手写动态代理。在环绕通知,可以控制方法的执行。可以和其他通知共同进行。
9.4 Spring事务注解
@Transactional
对方法中所有的操作作为一个事务进行管理,可以通过属性设置事务的传播行为、隔离机制、超时时间、只读属性、回滚机制。