Spring JDBC 和 事务控制
主要内容
Spring 整合 JDBC 环境
Spring 框架除了提供 IOC 与 AOP 核心功能外,同样提供了基于JDBC 的数据访问功能,使得访问持久层数据更加方便。使用 Spring JDBC 环境,首先需要一套 Spring 整合 JDBC 的环境。
添加依赖坐标
org.springframework
spring-context
5.2.4.RELEASE
org.springframework
spring-test
5.2.4.RELEASE
test
org.aspectj
aspectjweaver
1.9.5
org.springframework
spring-jdbc
5.2.4.RELEASE
org.springframework
spring-tx
5.2.4.RELEASE
mysql
mysql-connector-java
8.0.19
com.mchange
c3p0
0.9.5.5
添加 jdbc 配置文件
在src/main/resources目录下新建jdbc.properties配置文件,并设置对应的配置信息
# 驱动名
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接
jdbc.url=jdbc:mysql://localhost:3306/(数据库名称)?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
# 数据库用户名称
jdbc.user=(数据库账号)
# 数据库用户密码
jdbc.password=(数据库密码)
以下为可选配置
# 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3
initialPoolSize=20
# 指定连接池中保留的最大连接数. Default:15
maxPoolSize=100
# 指定连接池中保留的最小连接数
minPoolSize=10
# 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0
maxIdleTime=600
# 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数. Default:3
acquireIncrement=5
# JDBC的标准,用以控制数据源内加载的PreparedStatements数量。
maxStatements=5
# 每60秒检查所有连接池中的空闲连接.Default:0
idleConnectionTestPeriod=60
修改 spring 配置文件
spring.xml
配置数据源
由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。
C3P0 与 DBCP 二选一即可
DBCP(DataBase connection pool),数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件。单独使用dbcp需要2个包:commons-dbcp.jar,commons-pool.jar dbcp,没有自动回收空闲连接的功能。
C3P0是一个开源的JDBC连接池,它实现了数据源,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。c3p0有自动回收空闲连接功能。
C3P0 数据源配置
C3P0 其他额外配置(对应的值在jdbc.properties文件中指定)
DBCP 数据源配置
模板类配置
Spring把 JDBC 中重复的操作建立成了一个模板类:org.springframework.jdbc.core.JdbcTemplate 。
JDBC 测试
创建指定数据库
选择连接,右键选择"新建数据库",设置数据库的名称和编码格式
创建数据表
使用 JUnit 测试
通过 junit 测试 jdbcTemplate bean 是否获取到
JUnit 测试
public class SpringJdbcTest01 {
@Test
public void testQueryCount() {
// 获取spring上下文环境
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
// 得到模板类 JdbcTemplate对象
JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
// 定义sql语句
String sql = "select count(1) from tb_account";
// 执行查询操作(无参数)
Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("总记录数:" + total);
}
@Test
public void testQueryCountByUserId() {
// 获取spring上下文环境
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
// 得到模板类 JdbcTemplate对象
JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
// 定义sql语句
String sql = " select count(1) from tb_account where user_id = ?";
// 执行查询操作(有参数)
Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);
System.out.println("总记录数:" + total);
}
}
简单封装
public class SpringJdbcTest02 {
private JdbcTemplate jdbcTemplate;
@Before
public void init() {
// 得到Spring上下文环境
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
// 得到模板类 JdbcTemplate对象
jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
}
@Test
public void testQueryCount() {
// 定义sql语句
String sql = "select count(1) from tb_account";
// 执行查询操作(无参数)
Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("总记录数:" + total);
}
@Test
public void testQueryCountByUserId() {
// 定义sql语句
String sql = " select count(1) from tb_account where user_id = ?";
// 执行查询操作(有参数)
Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);
System.out.println("总记录数:" + total);
}
}
注解封装
@RunWith
就是一个运行器
@RunWith(JUnit4.class) 就是指用JUnit4来运行
@RunWith(SpringJUnit4ClassRunner.class) 让测试运行于Spring测试环境
@ContextConfiguration
Spring整合JUnit4测试时,使用注解引入多个配置文件
@ContextConfiguration(Locations="classpath:applicationContext.xml")
@ContextConfiguration(locations = {"classpath:spring.xml", "classpath:bean.xml"})
@RunWith(SpringJUnit4ClassRunner.class) // 将junit测试加到spring环境中
@ContextConfiguration(locations = {"classpath:spring.xml"}) // 设置要加载的资源文件
public class SpringJdbcTest03 {
@Resource
private JdbcTemplate jdbcTemplate;
@Test
public void testQueryCount() {
// 定义sql语句
String sql = "select count(1) from tb_account";
// 执行查询操作(无参数)
Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("总记录数:" + total);
}
}
通用封装
定义一个父类,设置通用的配置信息
/**
* 通用的测试环境,需要使用环境的直接继承类即可
*/
@RunWith(SpringJUnit4ClassRunner.class) // 将junit测试加到spring环境中
@ContextConfiguration(locations = {"classpath:spring.xml"}) // 设置要加载的资源文件
public class BaseTest {
}
继承通用的测试类
public class SpringJdbcTest04 extends BaseTest {
@Resource
private JdbcTemplate jdbcTemplate;
@Test
public void testQueryCount() {
// 定义sql语句
String sql = "select count(1) from tb_account";
// 执行查询操作(无参数)
Integer total= jdbcTemplate.queryForObject(sql, Integer.class);
System.out.println("总记录数:" + total);
}
}
持久层账户模块操作
当完成 Spring Jdbc 环境集成后,这里使用spring jdbc 完成账户单表crud 操作。
账户接口方法定义
定义实体类
Account.java
package com.xxxx.entity;
import java.util.Date;
/**
* 用户账户类
*/
public class Account {
private Integer accountId; // 账户ID,主键
private String accountName; // 账户名称
private String accountType; // 账户类型
private Double money; // 账户金额
private String remark; // 账户备注
private Integer userId; // 用户ID,账户所属用户
private Date createTime; // 创建时间
private Date updateTime; // 修改时间
public Account() {
}
public Account(String accountName, String accountType, Double money,
String remark, Integer userId) {
this.accountName = accountName;
this.accountType = accountType;
this.money = money;
this.remark = remark;
this.userId = userId;
}
@Override
public String toString() {
return "Account{" +
"accountId=" + accountId +
", accountName='" + accountName + ''' +
", accountType='" + accountType + ''' +
", money=" + money +
", remark='" + remark + ''' +
", userId=" + userId +
", createTime=" + createTime +
", updateTime=" + updateTime +
'}';
}
public Integer getAccountId() {
return accountId;
}
public void setAccountId(Integer accountId) {
this.accountId = accountId;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountType() {
return accountType;
}
public void setAccountType(String accountType) {
this.accountType = accountType;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
定义接口类
IAccountDao.java
package com.xxxx.dao;
import com.xxxx.entity.Account;
import java.util.List;
/**
* 用户模块 接口定义
* 1. 添加账户
* 添加账户记录,返回受影响的行数
* 添加账户记录,返回记录的主键
* 批量添加账户记录,返回受影响的行数
* 2. 查询账户
* 查询指定用户的账户总记录数,返回记录数
* 查询指定账户记录详情,返回账户对象
* 多条件查询指定用户的账户列表,返回账户集合
* 3. 更新账户
* 更新账户记录,返回受影响的行数
* 批量更新账户记录,返回受影响的行数
* 4. 删除账户
* 删除账户记录,返回受影响的行数
* 批量删除账户记录,返回受影响的行数
*/
public interface IAccountDao {
/**
* 添加账户
* 添加账户记录,返回受影响的行数
* @param account
* @return
*/
public int addAccount(Account account) ;
/**
* 添加账户
* 添加账户记录,返回记录的主键
* @param account
* @return
*/
public int addAccountHasKey(Account account);
/**
* 添加账户
* 批量添加账户记录,返回受影响的行数
* @param accounts
* @return
*/
public int addAccountBatch(List accounts);
/**
* 查询账户
* 查询指定用户的账户总记录数,返回记录数
* @param userId
* @return
*/
public int queryAccountCount(Integer userId);
/**
* 查询账户
* 查询指定账户记录详情,返回账户对象
* @param accountId
* @return
*/
public Account queryAccountById(Integer accountId);
/**
* 查询账户
* 多条件查询指定用户的账户列表,返回账户集合
* @param userId
* @param accountName
* @param accountType
* @param createTime
* @return
*/
public List queryAccountsByParams(Integer userId, String accountName, String accountType, String createTime);
/**
* 更新账户
* 更新账户记录,返回受影响的行数
* @param account
* @return
*/
public int updateAccountById(Account account);
/**
* 更新账户
* 批量更新账户记录,返回受影响的行数
* @param accounts
* @return
*/
public int updateAccountBatch(List accounts);
/**
* 删除账户
* 删除账户记录,返回受影响的行数
* @param accountId
* @return
*/
public Integer deleteAccoutById(Integer accountId);
/**
* 删除用户
* 批量删除账户记录,返回受影响的行数
* @param ids
* @return
*/
public int deleteAccountBatch(Integer[] ids);
}
定义接口实现类
package com.xxxx.dao.impl;
import com.xxxx.dao.IAccountDao;
import com.xxxx.entity.Account;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 账户模块接口实现类
*/
@Repository
public class AccountDaoImpl implements IAccountDao {
// JdbcTemplate 模板类注入
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public int addAccount(Account account) {
return 0;
}
@Override
public int addAccountHasKey(Account account) {
return 0;
}
@Override
public int addAccountBatch(List accounts) {
return 0;
}
@Override
public int queryAccountCount(Integer userId) {
return 0;
}
@Override
public Account queryAccountById(Integer accountId) {
return null;
}
@Override
public List queryAccountsByParams(Integer userId, String accountName, String accountType, String createTime) {
return null;
}
@Override
public int updateAccountById(Account account) {
return 0;
}
@Override
public int updateAccountBatch(List accounts) {
return 0;
}
@Override
public Integer deleteAccoutById(Integer accountId) {
return null;
}
@Override
public int deleteAccountBatch(Integer[] ids) {
return 0;
}
}
账户记录添加实现
在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记录等情况。这里对于账户记录添加方式分为三种方式:添加单条记录返回受影响行数、添加单条记录返回主键、批量添加多条记录。
添加账户记录
/**
* 添加单条记录,返回受影响的行数
* @param account
* @return
*/
@Override
public int addAccount(Account account) {
String sql = "insert into tb_account(account_name,account_type,money,remark," +
"user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
Object[] objs = {account.getAccountName(),account.getAccountType(),
account.getMoney(),account.getRemark(),account.getUserId()};
return jdbcTemplate.update(sql,objs);
}
测试方法
/**
* 添加账户记录,得到受影响的行数
*/
@Test
public void testAddAccount() {
// 准备要添加的数据
Account account = new Account("张三","建设银行",100.0,"零花钱",1);
// 调用对象的添加方法,返回受影响的行数
int row = accountDao.addAccount(account);
System.out.println("添加账户受影响的行数:" + row);
}
添加记录返回主键
/**
* 添加单条记录,返回主键
* @param account
* @return
*/
@Override
public int addAccountHasKey(Account account) {
String sql = "insert into tb_account(account_name,account_type,money,remark," +
"user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
// 定义keyHolder 对象 获取记录主键值
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
// 预编译sql语句,并设置返回主键
PreparedStatement ps =
connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// 设置参数
ps.setString(1,account.getAccountName());
ps.setString(2,account.getAccountType());
ps.setDouble(3,account.getMoney());
ps.setString(4,account.getRemark());
ps.setInt(5,account.getUserId());
return ps;
},keyHolder);
// 得到返回的主键
Integer key = keyHolder.getKey().intValue();
return key;
}
测试方法
/**
* 添加账户记录,返回主键
*/
@Test
public void testAddAccountHasKey() {
// 准备要添加的数据
Account account = new Account("李四","招商银行",200.0,"兼职费",2);
// 调用对象的添加方法,返回主键
int key = accountDao.addAccountHasKey(account);
System.out.println("添加账户返回的主键:" + key);
}
批量添加账户记录
/**
* 添加多条记录,返回受影响的行数
* @param accounts
* @return
*/
@Override
public int addAccountBatch(final List accounts) {
String sql = "insert into tb_account(account_name,account_type,money,remark," +
"user_id,create_time,update_time) values (?,?,?,?,?,now(),now())";
int rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement, int i)
throws SQLException {
// 设置参数
preparedStatement.setString(1,accounts.get(i).getAccountName());
preparedStatement.setString(2,accounts.get(i).getAccountType());
preparedStatement.setDouble(3,accounts.get(i).getMoney());
preparedStatement.setString(4,accounts.get(i).getRemark());
preparedStatement.setInt(5,accounts.get(i).getUserId());
}
@Override
public int getBatchSize() {
return accounts.size();
}
}).length;
return rows;
}
测试方法
/**
* 批量添加数据,返回受影响的行数
*/
@Test
public void testAddAccountBatch() {
// 准备要添加的数据
Account account = new Account("王五","农业银行",2000.0,"工资",3);
Account account2 = new Account("赵六","中国银行",280.0,"奖金",3);
Account account3 = new Account("田七","工商银行",800.0,"零花钱",3);
List accountList = new ArrayList<>();
accountList.add(account);
accountList.add(account2);
accountList.add(account3);
// 调用对象的添加方法,返回主键
int rows = accountDao.addAccountBatch(accountList);
System.out.println("批量添加账户受影响的行数:" + rows);
}
账户记录查询实现
账户记录查询这里提供了三种查询方式,查询指定用户所有账户记录数,查询单条账户记录详情,多条件查询指定用户账户记录。
查询用户的账户总记录数
/**
* 查询指定用户的账户总记录数,返回记录数
* @param userId
* @return
*/
@Override
public int queryAccountCount(Integer userId) {
String sql = "select count(1) from tb_account where user_id = ?";
int count = jdbcTemplate.queryForObject(sql,Integer.class,userId);
return count;
}
测试方法
/**
* 查询用户的账户总记录数,返回总记录数
*/
@Test
public void testQueryAccountCount(){
// 查询ID为1的用户的账户总记录数
int total = accountDao.queryAccountCount(1);
System.out.println("总记录数:" + total);
}
查询指定账户记录详情
/**
* 查询某个账户记录详情,返回账户对象
* @param accountId
* @return
*/
@Override
public Account queryAccountById(Integer accountId) {
String sql = "select * from tb_account where account_id = ?";
Account account = jdbcTemplate.queryForObject(sql, new Object[]{accountId}, (resultSet, i) -> {
Account acc = new Account();
acc.setAccountId(resultSet.getInt("account_id"));
acc.setMoney(resultSet.getDouble("money"));
acc.setAccountName(resultSet.getString("account_name"));
acc.setAccountType(resultSet.getString("account_type"));
acc.setRemark(resultSet.getString("remark"));
acc.setCreateTime(resultSet.getDate("create_time"));
acc.setUpdateTime(resultSet.getDate("update_time"));
acc.setUserId(resultSet.getInt("user_id"));
return acc;
});
return account;
}
测试方法
/**
* 查询指定账户的记录详情,返回账户对象
*/
@Test
public void testQueryAccountById(){
// 查询ID为1的账户记录的详情
Account account = accountDao.queryAccountById(1);
System.out.println("账户详情:" + account.toString());
}
多条件查询用户账户记录
/**
* 多条件查询指定用户的账户列表,返回账户集合
* @param userId 用户Id
* @param accountName 账户名称 (模糊查询)
* @param accountType 账户类型
* @param createTime 账户创建时间
* @return
*/
@Override
public List queryAccountsByParams(Integer userId, String accountName, String accountType,
String createTime) {
String sql = "select * from tb_account where user_id = ? ";
List