SpringFramework框架-JDBC
Spring JDBC ( Java Database Connectivity )是 Spring 框架对 JDBC API 的轻量级封装,旨在简化数据库操作代码,减少样板代码,同时保留直接使用 JDBC 的灵活性和性能
配置
修改JDBC配置文件,在resources目录下新建jdbc.properties配置文件,设置相应配置信息
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/databassName?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
jdbc.username=username
jdbc.password=password
|
可选配置如下
initialPoolSize=20
maxPoolSize=100
minPoolSize=10
maxIdlerime=600
acquireIncrement=5
maxStatements=5
idleConnectionTestPeriod=60
|
修改spring配置文件,加载jdbc.properties
<context:property-placeholder location="jdbc.properties"/>
|
为数据库连接池添加bean标签
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxPoolSize" value="${maxPoolSize}"/> </bean>
|
配置模板类,JDBC模板类是Spring JDBC的核心组件,它的主要作用是简化JDBC编程,解决传统JDBC代码中的重复和繁琐问题
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"/> </bean>
|
测试
运行以下代码,可以正常连接数据库
public class TestJdbc { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate"); String sql = "SELECT * FROM user"; List<Map<String, Object>> result = jdbcTemplate.queryForList(sql); System.out.println(result); } }
|
优化
实际运行中,在每个连接数据库的场景都配置上下文环境将会非常繁琐,可以使用@Before注解进行提前初始化,将获取模板类对象的操作剥离出来,这里的@Before注解是junit包下的,在每个测试方法执行前运行,用于测试环境的初始化
@Before public void init(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate"); }
|
由于两个方法被拆分,此时test()方法无法接收jdbcTemplate,此时可以将模板提升到属性字段
private JdbcTemplate jdbcTemplate;
@Before public void init(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml"); this.jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate"); }
|
其实还可以更进一步,将Junit的测试环境运行在Spring测试环境中,使用@RunWith注解完成,其接受一个测试运行器(Runner)类,用于指定 JUnit 如何运行测试。此外,还需要@ContextConfiguration指定配置文件,该注解有location属性接受一个集合,可用于加载多个配置文件
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring.xml"}) public class TestJdbc { @Resource private JdbcTemplate jdbcTemplate;
@Test public void test() { String sql = "SELECT * FROM user"; List<Map<String, Object>> result = jdbcTemplate.queryForList(sql); System.out.println(result); } }
|
Spring JDBC进行单表CRUD操作
我们用一个账号管理系统来演示使用JDBC与数据库交互
Account类
public class Account { private Integer UserId; private Integer accountId; private String accountName; private String accountType; private double balance; private String remark; private Date createTime; private Date updateTime;
public Account() {} public Account(int accountId, String accountName, String accountType, double balance, String remark) { this.accountId = accountId; this.accountName = accountName; this.accountType = accountType; this.balance = balance; this.remark = remark; } @Override public String toString() { return "Account{" + "accountId=" + accountId + ", accountName='" + accountName + '\'' + ", accountType='" + accountType + '\'' + ", balance=" + balance + ", remark='" + remark + '\'' + ", createTime=" + createTime + ", updateTime=" + updateTime + '}'; }
public Date getUpdateTime() { return updateTime; }
public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
public String getRemark() { return remark; }
public void setRemark(String remark) { this.remark = remark; }
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
public String getAccountType() { return accountType; }
public void setAccountType(String accountType) { this.accountType = accountType; }
public String getAccountName() { return accountName; }
public void setAccountName(String accountName) { this.accountName = accountName; }
public void setAccountId(int accountId) { this.accountId = accountId; }
public Integer getUserId() { return UserId; }
public void setUserId(Integer userId) { UserId = userId; }
public Integer getAccountId() { return accountId; }
public void setAccountId(Integer accountId) { this.accountId = accountId; }
|
数据添加
添加单条数据,返回受影响的行数
public int addAccount(Account account) { String sql = "insert into tb_account(account_name,account_type,balance,remark," + "user_id,create_time,update_time) values (?,?,?,?,now(),now());" Object[] objs = {account.getAccountName(),account.getAccountType(), account.getBalance(),account.getRemark(),account.getUserId()); return jdbcTemplate.update(sql,objs); }
|
添加单条数据,返回主键
public int addAccountHaskey(Account account) { String sql = "insert into tb_account(account_name,account_type,balance,remark,user_id,create_time.update_time) values (?,?,?,?,?,now(),now())"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS); ps.setString(1,account.getAccountName()); ps.setString(2,account.getAccountType()); ps.setDouble(3,account.getBalance()); ps.setString(4,account.getRemark()); ps.setInt(5,account.getUserId()); return ps; },keyHolder); Integer key = keyHolder.getKey().intValue(); return key; }
|
update()方法的一个参数PreparedStatementCreator是一个接口,故此处直接传入了一个lambda表达式,而不是该lambda表达式的返回值,lambda表达式本身就是一个自动实现PreparedStatementCreator接口的对象,update()方法内部调用接口中的createPreparedStatement()获得配置好的 PreparedStatement执行 SQL
批量添加数据,返回受影响的行数
public int addAccountBatch(final List<Account> accounts) { String sql = "insert into tb_account(account_name,account_type,balance,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).getBalance()); preparedStatement.setString(4, accounts.get(i).getRemark()); preparedStatement.setInt(5, accounts.get(i).getUserId()); } @Override public int getBatchSize() { return accounts.size(); } }).length; return rows; }
|
数据查询
查询指定用户的信息数
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; }
|
查询指定用户的账户详情
public Account queryAccountById(Integer accountId) { String sql = "select * from tb_account where account_id = ?"; Account account = jdbcTemplate.queryForObject(sql, new Object[]{accountId}, (ResultSet resultSet,int i) -> { Account acc = new Account(); acc.setAccountId(resultSet.getInt("account_id")); acc.setBalance(resultSet.getDouble("Balance")); 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; }
|
多条件查询用户账户信息搭配模糊搜索
public List<Account> queryAccountByParams(Interger userId, String accountName, String accountType, String createTime) { String sql = "select * from tb_account where user_id = ?"; List<Object> params = new ArrayList<>(); params.add(userId); if(StringUtils.isNotBlank(accountName)) { sql += " and account_name like concat('%',?,'%')"; params.add(accountName); } if(StringUtils.isNotBlank(accountType)) { sql += " and account_type = ?"; params.add(accountType); } if(StringUtils.isNotBlank(creatTime)){ sql += " and creat_time < ?"; params.add(creatTime); } Object[] objs = params.toArray(); List<Account> accounts = jdbcTemplate.queryForObject(sql, objs, (ResultSet resultSet,int i) -> { Account acc = new Account(); acc.setAccountId(resultSet.getInt("account_id")); acc.setBalance(resultSet.getDouble("Balance")); acc.setAccountName(resultSet.getString("account_name")); acc.setAccountType(resultSet.getString("account_type")); acc.setRemark(resultSet.getString("remark")); acc.setCreateTime(resultSet.getTimestamp("create_time")); acc.setUpdateTime(resultSet.getTimestamp("update_time")); acc.setUserId(resultSet.getInt("user_id")); return acc; }); return accounts; }
|
params一开始用数组后面又转回数组的原因是传入的参数未定,在经过后面的if判断后才能确定传入的参数个数
数据更新
更新账户信息,返回更新的行数
public int updateAccountById(Account account) { String sql = "update tb_account set account_name = ?, account_type = ?, " + " balance = ? ,remark = ?,user_id = ? ,update_time = now() " + " where account_id = ? "; Object[] objs = {account.getAccountName(), account.getAccountType(), account.getBalance(), account.getRemark(), account.getUserId(), account.getAccountId()}; return jdbcTemplate.update(sql, objs); }
|
批量更新账户信息,返回受影响的行数
public int updateAccountBatch(List<Account> accounts) { String sql = "update tb_account set account_name = ?, account_type = ?, " + " balance = ? ,remark = ?,user_id = ? ,update_time = now() " + " where account_id = ? "; int[] rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, accounts.get(i).getAccountName()); ps.setString(2, accounts.get(i).getAccountType()); ps.setDouble(3, accounts.get(i).getBalance()); ps.setString(4, accounts.get(i).getRemark()); ps.setInt(5, accounts.get(i).getUserId()); ps.setInt(6, accounts.get(i).getAccountId()); } @Override public int getBatchSize() { return accounts.size(); } }).length; return rows; }
|
数据删除
删除账户信息,返回更新的行数
public Integer deleteAccountById(Integer accountId) { String sql = "delete from tb_account where account_id = ?"; Object[] objs = {accountId}; return jdbcTemplate.update(sql, objs); }
|
批量删除账户信息,返回更新的行数
public int deleteAccountBatch(Integer[] ids) { String sql = "delete from tb_account where account_id = ?"; int[] rows = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setInt(1, ids[i]); }
@Override public int getBatchSize() { return ids.length; } }).length; return rows; }
|
SpringFramework框架-事务控制
Spring事务管理让多个数据库操作要么全部成功要么全部失败,通过@Transactional注解自动处理事务开始、提交和回滚,确保数据一致性,避免手动管理事务的繁琐代码
事务的特性
原子性(Atomicity)
要么全部成功,要么全部失败
一致性(Consistency)
事务在执行前后,数据库中数据要保持一致性状态。(如转账的过程 账户操作后数据必须保持一致)
隔离性(Isolation)
事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对同一记录进行操作必须保证没有任何干扰),当然没有影响是不可能的,为了让影响级别降到最低,通过隔离级别加以限制:
| 隔离级别 |
英文名称 |
脏读 |
不可重复读 |
幻读 |
描述 |
| 读未提交 |
READ_UNCOMMITTED |
✓ |
✓ |
✓ |
隔离级别最低,会引发脏读、不可重复读和幻读问题 |
| 读已提交 |
READ_COMMITTED |
✗ |
✓ |
✓ |
只能读取已提交的数据,避免了脏读,但仍有不可重复读和幻读问题 |
| 可重复读 |
REPEATABLE_READ |
✗ |
✗ |
✓ |
确保同一事务中多次读取同一数据结果一致,避免了脏读和不可重复读,但仍存在幻读问题 |
| 串行化 |
SERIALIZABLE |
✗ |
✗ |
✗ |
最严格的隔离级别,事务串行执行,完全避免脏读、不可重复读和幻读问题,但性能最低 |
持久性(Durability)
事务提交完毕后,数据库中的数据变更是永久的
事务接口
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,该接口提供入下方法
public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException; }
|
JDBC事务管理
当应用程序直接使用 JDBC 进行数据持久化时,Spring 通过 DataSourceTransactionManager 来管理事务边界。配置方式如下
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
|
DataSourceTransactionManager 通过从 DataSource 获取 java.sql.Connection 对象来管理事务,事务提交时调用 Connection.commit() 方法,事务回滚时调用 Connection.rollback() 方法
Hibernate 事务管理
当应用程序使用 Hibernate 作为持久化框架时,Spring 通过 HibernateTransactionManager 来管理事务。对于 Hibernate 3,配置如下
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean>
|
HibernateTransactionManager 需要注入 Hibernate 的 SessionFactory,该管理器将事务管理职责委托给 Hibernate 的 org.hibernate.Transaction 对象,Transaction 对象从 Hibernate Session 中获取,事务成功完成时调用 Transaction.commit() 方法,事务失败时调用 Transaction.rollback() 方法
JPA(Java持久化API)事务管理
当应用程序使用 JPA 作为持久化标准时,Spring 通过 JpaTransactionManager 来管理事务。配置方式如下
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
|
JpaTransactionManager 需要装配一个 JPA 实体管理工厂(javax.persistence.EntityManagerFactory 的实现),该管理器与 JPA EntityManager 协作来构建和管理事务,通过 EntityManagerFactory 获取 EntityManager,并在其基础上进行事务控制
JTA(Java事务API)事务管理
当应用程序涉及分布式事务或多个事务资源(如多个不同数据源)时,Spring 通过 JtaTransactionManager 来管理事务。配置方式如下
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManagerName" value="java:/TransactionManager" /> </bean>
|
JtaTransactionManager 将事务管理职责委托给 Java 事务 API(JTA),具体委托给 javax.transaction.UserTransaction 和 javax.transaction.TransactionManager 对象,事务成功时调用 UserTransaction.commit() 方法提交,事务失败时调用 UserTransaction.rollback() 方法回滚
XML配置文件实现事务管理
添加命名空间
事务命名空间
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
|
AOP命名空间
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
|
设置AOP代理
配置事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
|
配置通知
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="query*" read-only="true" /> </tx:attributes> </tx:advice>
|
属性含义与可选值
| 属性名 |
是否必须 |
默认值 |
可选值 |
说明 |
| name |
必须 |
无 |
方法名/通配符 |
与事务关联的方法名,支持通配符:
* - 匹配所有方法
get* - 匹配以”get”开头的方法 handle* - 匹配以”handle”开头的方法
on*Event - 匹配如”onClickEvent”等方法 |
| propagation |
可选 |
REQUIRED |
REQUIRED - 支持当前事务,如果不存在则创建新事务 SUPPORTS - 支持当前事务,如果不存在则以非事务方式执行 MANDATORY - 支持当前事务,如果不存在则抛出异常 REQUIRES_NEW - 创建新事务,如果存在当前事务则挂起 NOT_SUPPORTED - 以非事务方式执行,如果存在当前事务则挂起 NEVER - 以非事务方式执行,如果存在事务则抛出异常 NESTED - 如果当前存在事务,则在嵌套事务内执行 |
事务传播行为,控制方法如何参与事务 |
| isolation |
可选 |
DEFAULT |
DEFAULT - 使用数据库默认隔离级别 READ_UNCOMMITTED - 读未提交 READ_COMMITTED - 读已提交 REPEATABLE_READ - 可重复读 SERIALIZABLE - 串行化 |
事务隔离级别,控制事务并发访问的隔离程度 |
| timeout |
可选 |
-1 |
正整数(秒) |
事务超时时间: -1 - 永不超时
30 - 30秒后超时回滚
60 - 60秒后超时回滚 |
| read-only |
可选 |
false |
true - 只读事务 false - 读写事务 |
事务是否只读,只读事务可进行查询优化 |
| rollback-for |
可选 |
无 |
异常类全限定名(逗号分隔) |
指定哪些异常触发回滚: java.lang.Exception - 所有Exception
com.example.BusinessException - 特定业务异常,默认回滚RuntimeException和Error |
| no-rollback-for |
可选 |
无 |
异常类全限定名(逗号分隔) |
指定哪些异常不触发回滚:
java.sql.SQLException - SQL异常不触发回滚
com.example.NoRollbackException - 特定不回滚异常 |
配置AOP
<aop:config> <aop:pointcut expression="execution(* com.xxxx.service..*.*(..))" id="cut" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"/> </aop:config>
|
初现端倪了,这仔细一看还是AOP动态代理那一块
注解实现事务管理
配置事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
|
配置注解支持
<tx:annotation-driven transaction-manager="txManger" />
|
添加事务注解
在需要配置事务管理的方法上加入事务注解
@Transactional(propagation=Propagation.REQUIRED) public void saveUser(String userName, String userPwd) {
}
|
@Transactional注解具有XML配置中列举的所有属性,可以根据需求自己配置