SSH框架之旅-spring(3)
1.Spring 的 jdbcTemplate操作
Spring 框架是一站式的框架,针对 JavaEE 的三层结构,每一层都有解决的技术,在 DAO(数据操作层)使用 jdbcTempalte。并且 Spring 也是一个胶水式的框架,对于第三方的框架也有很好的整合支持,对不同的持久层技术都进行封装。
ORM 持久化技术 | 模板类 |
---|---|
JDBC | org.springframework.core.JdbcTemplate |
Hibernate5.x | org.springframework.hibernate5.HibernateTemplate |
IBatis(MyBatis) | org.springframework.orm.ibatis.sqlMapClientTemplate |
JPA | org.springframework.orm.jpa.JpaTemplate |
下面是 Spring 对数据库的 CURD 操作。
1.1 准备工作
Spring 对 jdbc 做了封装,需要再之前 Spring 的 jar 包的基础上导入 spring-jdbc.jar
和 spring-tx.jar
包,另外记得导入数据库的驱动包。
创建数据库和数据表,Spring 中的 JdbcTemplate 只是对 Jdbc 做了封装,不会为我们自动创建数据表
数据表结构.png实体类
package cc.wenshixin.jdbc;
public class Student {
private int id;
private String name;
private String sex;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", sex=" + sex + "]";
}
}
增删改操作都是调用 update()
方法,Spring 中重载了很多 update()
方法。
1.2 增加操作
@Test
//添加操作
public void add()
{
//设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建jdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//调用JdbcTemplate对象里面的方法实现操作
String sql = "INSERT INTO student(name,sex) VALUES(?,?)";
int rows = jdbcTemplate.update(sql, "小明", "男");
System.out.println(rows);
}
1.3 删除操作
@Test
//删除操作
public void delete()
{
//设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建jdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//调用JdbcTemplate对象里面的方法实现操作
String sql = "DELETE FROM student WHERE id=?";
int rows = jdbcTemplate.update(sql, 2);
System.out.println(rows);
}
1.4 修改操作
@Test
//修改操作
public void update()
{
//设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建jdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//调用JdbcTemplate对象里面的方法实现操作
String sql = "UPDATE student SET name=? WHERE id=?";
int rows = jdbcTemplate.update(sql, "小张", 2);
System.out.println(rows);
}
1.5 查询操作
基础的jdbc查询操作,必会,虽然使用框架不会使用这些基础的代码,但是在无法使用框架时,也可使用基础的方法实现。
//使用jdbc操作数据库的基本步骤
@Test
public void testJdbc() throws SQLException
{
Connection conn = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//创建连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false", "root", "root");
//编写sql语句
String sql = "SELECT * FROM student WHERE id=?";
//预编译sql
psmt = conn.prepareStatement(sql);
//设置参数
psmt.setInt(1, 1);
//执行sql
rs = psmt.executeQuery();
//遍历结果集
while(rs.next())
{
//返回得到的结果集
int id = rs.getInt("id");
String name = rs.getString("name");
String sex = rs.getString("sex");
//放到对象中
Student student = new Student();
student.setId(id);
student.setName(name);
student.setSex(sex);
System.out.println(student);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if(rs != null)
{
rs.close();
}
if(psmt != null)
{
psmt.close();
}
if(conn != null)
{
conn.close();
}
}
}
- 查询数量
查询数量使用 queryForObject()
方法。
@Test
//查询数量操作
public void queryCount()
{
//设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建jdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//调用JdbcTemplate对象里面的方法实现操作
String sql = "SELECT COUNT(id) FROM student";
int count = jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println(count);
}
查询对象时,要先实现实体类的查询实现了 Spring 查询接口 RowMapper
的实现类。
实体类的查询接口实现类
package cc.wenshixin.jdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
public class StudentRowMapper implements RowMapper<Student>{
@Override
public Student mapRow(ResultSet rs, int num) throws SQLException {
//从结果集中得到数据
int id = rs.getInt("id");
String name = rs.getString("name");
String sex = rs.getString("sex");
//把得到的结果封装到对象里面
Student student = new Student();
student.setId(id);
student.setName(name);
student.setSex(sex);
return student;
}
}
- 查询单个对象
@Test
//查询单个对象操作
public void queryObject()
{
//设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建jdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//调用JdbcTemplate对象里面的方法实现操作
String sql = "SELECT * FROM student WHERE id=?";
Student student = jdbcTemplate.queryForObject(sql, new StudentRowMapper(), 1);
System.out.println(student.toString());
}
查询多个对象
@Test
//查询多个对象操作
public void queryList()
{
//设置数据库信息
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
//创建jdbcTemplate对象,设置数据源
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//调用JdbcTemplate对象里面的方法实现操作
String sql = "SELECT * FROM student";
List<Student> studentList = jdbcTemplate.query(sql, new StudentRowMapper());
System.out.println(studentList);
}
2.Spring 配置连接池
在做 jdbc 操作时,不是每次都连接数据库,大多是创建一个数据库连接池,每次连接在这个数据库连接池中取连接对象,减少数据库连接和释放的操作。
2.1 准备工作
首先要导入配置 c3p0 连接池的 jar 包,c3p0.jar
和 mchange.jar
,下载地址,然后创建 Spring 的配置文件。
2.2 配置连接池
以上面的 student 实体类为例,通过 service 类,dao 类,插入一条数据。
配置文件
属性注入数据库配置信息和 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"
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">
<!-- 配置c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入属性值 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useSSL=false"></property>
<property name="user" value=""></property>
<property name="password" value=""></property>
</bean>
<!-- 配置创建对象 -->
<bean id="studentService" class="cc.wenshixin.service.StudentService"></bean>
<bean id="studentDao" class="cc.wenshixin.dao.StudentDao"></bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 把dataSource传递到模板对象里面 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解扫描 -->
<context:component-scan base-package="cc.wenshixin"></context:component-scan>
</beans>
DAO 类
package cc.wenshixin.dao;
import javax.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
public class StudentDao {
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
//插入数据
public void add()
{
String sql = "INSERT INTO student(name,sex) VALUES(?,?)";
jdbcTemplate.update(sql,"老王","男");
}
}
Service 类
package cc.wenshixin.service;
import javax.annotation.Resource;
import cc.wenshixin.dao.StudentDao;
public class StudentService {
@Resource(name="studentDao")
private StudentDao studentDao;
//添加操作
public void add()
{
studentDao.add();
}
}
测试类
package cc.wenshixin.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cc.wenshixin.service.StudentService;
public class TestC3p0 {
@Test
public void test1()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
StudentService service = (StudentService) context.getBean("studentService");
service.add();
}
}
3.Spring 事务管理
3.1 事务的基本概念
事务是一组逻辑上的操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败。
事务的特性:
- 原子性:强调事务是不可分割
- 一致性:事务的执行的前后数据的完整性保持一致
- 隔离性:一个事务执行的过程中,不应该收到其他事务的干扰
- 持久性:事务一旦结束,数据就持久到数据库
如果不考虑到隔离性引发的安全性问题:
- 脏读:一个事务读到了另一个事务的未提交的数据
- 不可重复读:一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致
- 虚读:一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致
解决读的问题:设置事务的隔离级别
- 未提交读:脏读,不可重复读,虚读都有可能发生
- 已提交读:避免脏读,但是不可重复读和虚读有可能发生
- 可重复读:避免脏读和不可重复读,但是虚读有可能发生
- 串行化:避免以上所有读的问题
补充:Mysql 数据库默认的事务隔离级别为可重复读。
3.2 Spring 事务管理的 API
Spring 事务管理有两种方式,一种是编程式事务管理,不使用,另一种式声明式事务管理,基于xml配置文件实现和基于注解实现。
Spring 平台事务管理器的接口 PlatformTransactionManager
,针对不同的 DAO 层的框架提供了接口不同的实现类。
事务管理器接口实现类 | 适用框架 |
---|---|
org.springframework.jdbc.datasource.DataSourceTransactionManager | 使用 Spring JDBC或IBatis进行持久化数据时使用 |
org.springframework.orm.hibernate3.HibernateTransactionManager | 使用 Hibernate5.x 版本进行持久化数据时使用 |
下面通过一个银行转账的例子说明 Spring API 的使用
创建数据表,并添加两条实验的数据
初始数据.pngDAO 类
package cc.wenshixin.dao;
import javax.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
public class DealDao {
@Resource(name="jdbcTemplate")
private JdbcTemplate jdbcTemplate;
public void subMoney(String name, int money)
{
String sql = "UPDATE yinhang SET money=money-? WHERE name=?";
jdbcTemplate.update(sql, money, name);
}
public void addMoney(String name, int money)
{
String sql = "UPDATE yinhang SET money=money+? WHERE name=?";
jdbcTemplate.update(sql, money, name);
}
}
Service 类
package cc.wenshixin.service;
import javax.annotation.Resource;
import cc.wenshixin.dao.DealDao;
public class DealService {
@Resource(name="dealDao")
private DealDao dealDao;
public void deal()
{
//小明转账给小黑1000元
dealDao.subMoney("小明", 1000);
dealDao.addMoney("小黑", 1000);
}
}
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">
<!-- 配置数据库信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入属性值 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useSSL=false"></property>
<property name="user" value=""></property>
<property name="password" value=""></property>
</bean>
<!-- 配置对象 -->
<bean id="dealService" class="cc.wenshixin.service.DealService"></bean>
<bean id="dealDao" class="cc.wenshixin.dao.DealDao"></bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 把dataSource传递到模板对象里面 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解扫描 -->
<context:component-scan base-package="cc.wenshixin"></context:component-scan>
</beans>
测试类
package cc.wenshixin.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cc.wenshixin.service.DealService;
public class DealTest {
@Test
public void test()
{
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
DealService dealService = (DealService) context.getBean("dealService");
dealService.deal();
}
}
无异常产生,结果为正常
无异常结果.png下面模拟异常情况的发生,实际情况为银行系统出错了,网络中断等等。
在 Service 类中加入如下代码:
//小明转账给小黑1000元
dealDao.subMoney("小明", 1000);
int i = 10 / 0; //模拟异常
dealDao.addMoney("小黑", 1000);
再次执行结果,出现除0的异常,但是数据库的结果为
模拟异常结果.png小明少了1000元,但因为异常,小黑并没有增加1000元,这样肯定是不行的,出现异常需要进行事务回滚。
将配置文件改成下面的配置文件,相比上面的配置文件,加入了事务的约束和事务管理器的配置,另外在要使用事务的方法所在类的上面加上 @Transactional
,这个加不加都不影响结果,但是便于代码维护最好加上。
<?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"
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/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
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置数据库信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 注入属性值 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useSSL=false"></property>
<property name="user" value=""></property>
<property name="password" value=""></property>
</bean>
<!-- 配置对象 -->
<bean id="dealService" class="cc.wenshixin.service.DealService"></bean>
<bean id="dealDao" class="cc.wenshixin.dao.DealDao"></bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 把dataSource传递到模板对象里面 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解扫描 -->
<context:component-scan base-package="cc.wenshixin"></context:component-scan>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
注入dataSource
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务增强 -->
<tx:advice id="txadvice" transaction-manager="transactionManager">
<!-- 做事务操作 -->
<tx:attributes>
<!-- 设置进行事务操作的方法匹配规则 -->
<tx:method name="deal*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* cc.wenshixin.service.DealService.*(..))" id="pointcut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"/>
</aop:config>
</beans>
再次执行,出现异常,但是数据库的结果并不会有任何变化。