Spring JDBC與事務管理
阿新 • • 發佈:2021-01-07
技術標籤:Spring學習資料庫mysqlspringjava
Spring JDBC與事務管理
Spring JDBC
- Spring JDBC是Spring框架用於處理關係型資料庫的模組
- Spring JDBC對JDBC API進行封裝,極大簡化開發工作量
- JdbcTemplate是Spring JDBC核心類,提供資料CRUD方法
Spring JDBC的使用步驟
- Maven工程引入依賴spring-jdbc
- applicationContext.xml配置DataSource資料來源
- 在Dao注入JdbcTemplate物件,實現資料CRUD
JdbcTemplate實現增刪改查
Spring JDBC配置過程程式碼示例:
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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 資料來源 --> <bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <!--JdbcTemplate提供CRUD的API--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="employeeDao" class="com.imooc.spring.jdbc.dao.EmployeeDao"> <!--為Dao注入JdbcTemplate物件--> <property name="jdbcTemplate" ref="jdbcTemplate"></property> </bean> </beans>Employee.java
package com.imooc.spring.jdbc.entity; import java.util.Date; /** * @author Rex * @create 2021-01-05 14:44 */ public class Employee { private Integer eno; private String ename; private Float salary; private String dname; private Date hiredate; public Integer getEno() { return eno; } public void setEno(Integer eno) { this.eno = eno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public Float getSalary() { return salary; } public void setSalary(Float salary) { this.salary = salary; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } @Override public String toString() { return "Employee{" + "eno=" + eno + ", ename='" + ename + '\'' + ", salary=" + salary + ", dname='" + dname + '\'' + ", hiredate=" + hiredate + '}'; } }
EmployeeDao.java
package com.imooc.spring.jdbc.dao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /** * @author Rex * @create 2021-01-05 14:46 */ @Repository public class EmployeeDao { private JdbcTemplate jdbcTemplate; public Employee findById(Integer eno){ String sql = "select * from employee where eno = ?"; Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class)); return employee; } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
SpringApplication.java
package com.imooc.spring.jdbc; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author Rex * @create 2021-01-05 14:52 */ public class SpringApplication { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); EmployeeDao employeeDao = context.getBean("employeeDao", EmployeeDao.class); Employee employee = employeeDao.findById(3308); System.out.println(employee); } }
JDBC Template的資料查詢程式碼示例:
EmployeeDao.java
public Employee findById(Integer eno){ String sql = "select * from employee where eno = ?"; //查詢單條資料 Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class)); return employee; } public List<Employee> findByDname(String dname){ String sql = "select * from Employee where dname = ?"; //查詢複合資料 List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class)); return list; } public List<Map<String, Object>> findMapByDname(String dname){ String sql = "select eno as empno, salary as s from employee where dname=?"; //將查詢結果作為Map進行封裝 List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname}); return maps; }
單條查詢測試
@Test public void testFindById(){ Employee employee = employeeDao.findById(3308); System.out.println(employee); }
查詢結果:
[Employee{eno=3308, ename='張三', salary=6000.0, dname='研發部', hiredate=2011-05-08 00:00:00.0}, Employee{eno=3420, ename='李四', salary=8700.0, dname='研發部', hiredate=2006-11-11 00:00:00.0}]
多條記錄查詢測試
@Test public void testFindByDname(){ List<Employee> list = employeeDao.findByDname("研發部"); System.out.println(list); }
查詢結果:
Employee{eno=3308, ename='張三', salary=6000.0, dname='研發部', hiredate=2011-05-08 00:00:00.0}
將查詢結果以Map作為封裝測試
@Test public void testFindMapByDname(){ System.out.println(employeeDao.findMapByDname("研發部")); }
查詢結果:
[{empno=3308, s=6000.0}, {empno=3420, s=8700.0}]
JdbcTemplate資料寫入程式碼示例
資料新增:
public int insert(Employee employee){ String sql = "insert into employee(eno, ename, salary, dname, hiredate) values(?, ?, ?, ?, ?)"; //利用update方法實現資料寫入操作 return jdbcTemplate.update(sql, new Object[]{employee.getEno(), employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate()}); }
測試程式碼:
@Test public void testInsert(){ Employee employee = new Employee(); employee.setEno(8888); employee.setEname("趙六"); employee.setSalary(6666f); employee.setDname("研發部"); employee.setHiredate(new Date()); int count = employeeDao.insert(employee); System.out.println("本次新增"+count+"條資料"); }
測試結果:
本次插入1條資料
資料修改:
public int update(Employee employee){ String sql = "update employee set ename = ?, salary = ?, dname = ?, hiredate = ? where eno = ?"; int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()}); return count; }
測試程式碼:
@Test public void testUpdate(){ Employee employee = employeeDao.findById(8888); employee.setSalary(employee.getSalary()+1000f); int count = employeeDao.update(employee); System.out.println("本次更新"+count+"條資料"); }
測試結果:
本次更新1條資料
資料刪除:
public int delete(Integer eno){ String sql = "delete from employee where eno = ?"; return jdbcTemplate.update(sql, new Object[]{eno}); }
測試程式碼:
@Test public void testDelete(){ int count = employeeDao.delete(8888); System.out.println("本次刪除"+count+"條資料"); }
測試結果:
本次刪除1條資料
Spring程式設計式事務
什麼是事務
- 事務以一種可靠的、一致的方式,訪問和造作資料庫的程式單元
- 說人話:要麼把事情做完,要麼什麼都不做,不要做一半
- 事務依賴於資料庫實現,MySQL通過事務區作為資料緩衝地帶
程式設計式事務
- 程式設計式事務是指通過程式碼手動提交回滾事務的事務控制方法
- SpringJDBC通過TransactionManage事務管理器實現事務控制
- 事務管理器提供commit/rollback方法進行事務提交與回滾
程式碼示例:
ApplicationContext.xml
<bean id="employeeService" class="com.imooc.spring.jdbc.service.EmployeeService"> <property name="employeeDao" ref="employeeDao"></property> <property name="transactionManager" ref="transactionManager"></property> </bean> <!--事務管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
EmployeeService.java
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import java.util.Date; /** * @author Rex * @create 2021-01-05 16:31 */ public class EmployeeService { private EmployeeDao employeeDao; private DataSourceTransactionManager transactionManager; public void batchImport(){ //定義了事務預設的標準配置 TransactionDefinition definition = new DefaultTransactionDefinition(); //開始一個事務 TransactionStatus status = transactionManager.getTransaction(definition); try { for (int i = 0; i < 10; i++) { if ( i == 3) { throw new RuntimeException("意料之外的異常"); } Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("員工" + i); employee.setSalary(4000f); employee.setDname("市場部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } // //提交事務 transactionManager.commit(status); }catch (RuntimeException e){ transactionManager.rollback(status); throw e; } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } public DataSourceTransactionManager getTransactionManager() { return transactionManager; } public void setTransactionManager(DataSourceTransactionManager transactionManager) { this.transactionManager = transactionManager; } }
JdbcTemplateTestor.java
@Test public void testBatchImport(){ employeeService.batchImport(); System.out.println("批量匯入成功"); }
測試結果:
... 16:55:40.411 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [insert into employee(eno, ename, salary, dname, hiredate) values(?, ?, ?, ?, ?)] 16:55:40.490 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQLWarning ignored: SQL state '22007', error code '1292', message [Incorrect date value: '2021-01-05 16:55:40.405' for column 'hiredate' at row 1] 16:55:40.498 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update 16:55:40.498 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [insert into employee(eno, ename, salary, dname, hiredate) values(?, ?, ?, ?, ?)] 16:55:40.502 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQLWarning ignored: SQL state '22007', error code '1292', message [Incorrect date value: '2021-01-05 16:55:40.498' for column 'hiredate' at row 1] 16:55:40.502 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL update 16:55:40.503 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - Executing prepared SQL statement [insert into employee(eno, ename, salary, dname, hiredate) values(?, ?, ?, ?, ?)] 16:55:40.507 [main] DEBUG org.springframework.jdbc.core.JdbcTemplate - SQLWarning ignored: SQL state '22007', error code '1292', message [Incorrect date value: '2021-01-05 16:55:40.502' for column 'hiredate' at row 1] 16:55:40.507 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Initiating transaction rollback 16:55:40.507 [main] DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Rolling back JDBC transaction on Connection [[email protected]] 16:55:40.521 [main] DEBUG ... java.lang.RuntimeException: 意料之外的異常...
Spring宣告式事務
宣告式事務
- 宣告式事務指在不修改原始碼情況下通過配置形式自動實現事務控制,宣告式事務本質就是AOP環繞通知
- 當目標方法執行成功時,自動提交事務
- 當目標方法丟擲執行時異常時,自動事務回滾
配置過程
- 配置TransactionManager事務管理器
- 配置事務通知與事務屬性
- 為事務通知繫結PointCut切點
程式碼示例:
applicationContext.xml
<!--1.事務管理器,用於建立事務/提交/回滾--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--2.事務通知配置,決定哪些方法使用事務,哪些方法不使用事務--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--目標方法名為batchImport時,啟用宣告式事務,成功提交,執行時異常回滾--> <tx:method name="batchImport" propagation="REQUIRED"/> <tx:method name="batch*" propagation="REQUIRED"></tx:method> <!--設定所有findXXX方法不需要使用事務--> <tx:method name="find*" propagation="NOT_SUPPORTED" read-only="true"/> <tx:method name="get*" propagation="NOT_SUPPORTED" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--3.定義宣告式事務的作用範圍--> <aop:config> <aop:pointcut id="pointcut" expression="execution(public * com.imooc..*Service.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"></aop:advisor> </aop:config>
EmployeeService.java
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import java.util.Date; public class EmployeeService { private EmployeeDao employeeDao; public void batchImport() { for (int i = 1; i <= 10; i++) { if(i == 3){ throw new RuntimeException("意料之外的異常"); } Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("員工" + i); employee.setSalary(4000f); employee.setDname("市場部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
執行結果:
同上
事務傳播行為
- 事務傳播行為是指多個擁有事務的方法在巢狀呼叫時的事務控制方式
- XML:<tx:method name="…" propagation=“REQUIRED”>
- 註解:@Transactional(propagation=Propagation.REQUIRED)
事務傳播行為七種型別
事務傳播型別 | 說明 |
---|---|
PROPAGATION_REQUIRED | 如果當前沒有事務,就新建一個事務,如果已經存在一個事務,加入到這個事務中。這是最常見的選擇 |
PROPAGATION_SUPPORTS | 支援當前事務,如果當前沒有事務,就以非事務方式執行 |
PROPAGATION_MANADATORY | 使用當前的事務,如果當前沒有事務,就丟擲異常 |
PROPAGATION_REQUIRES_NEW | 新建事務,如果當前存在事務,就把當前事務掛起 |
PROPAGATION_NOT_SUPPORT | 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起 |
PROPAGATION_NEVER | 以非事務方式執行,如果當前存在事務,則丟擲異常 |
PROPAGATION_NESTED | 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作 |
Spring註解配置宣告式事務
程式碼示例
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" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx https://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 base-package="com.imooc"/> <!--資料來源--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&usePublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--JdbcTemplate--> <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> <!--啟用註解形式宣告式事務--> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
Employee.java
package com.imooc.spring.jdbc.entity; import java.util.Date; public class Employee { private Integer eno; private String ename; private Float salary; private String dname; private Date hiredate; public Integer getEno() { return eno; } public void setEno(Integer eno) { this.eno = eno; } public String getEname() { return ename; } public void setEname(String ename) { this.ename = ename; } public Float getSalary() { return salary; } public void setSalary(Float salary) { this.salary = salary; } public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public Date getHiredate() { return hiredate; } public void setHiredate(Date hiredate) { this.hiredate = hiredate; } @Override public String toString() { return "Employee{" + "eno=" + eno + ", ename='" + ename + '\'' + ", salary=" + salary + ", dname='" + dname + '\'' + ", hiredate=" + hiredate + '}'; } }
EmployeeDao.java
package com.imooc.spring.jdbc.dao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import javax.annotation.Resource; import java.util.List; import java.util.Map; @Repository public class EmployeeDao { @Resource private JdbcTemplate jdbcTemplate; public Employee findById(Integer eno){ String sql = "select * from employee where eno = ?"; //查詢單條資料 Employee employee = jdbcTemplate.queryForObject(sql, new Object[]{eno}, new BeanPropertyRowMapper<Employee>(Employee.class)); return employee; } public List<Employee> findByDname(String dname){ String sql = "select * from employee where dname = ?"; //查詢複合資料 List<Employee> list = jdbcTemplate.query(sql, new Object[]{dname}, new BeanPropertyRowMapper<Employee>(Employee.class)); return list; } public List<Map<String, Object>> findMapByDname(String dname){ String sql = "select eno as empno , salary as s from employee where dname = ?"; //將查詢結果作為Map進行封裝 List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql, new Object[]{dname}); return maps; } public void insert(Employee employee){ String sql = "insert into employee(eno,ename,salary,dname,hiredate) values(?,?,?,?,?)"; //利用update方法實現資料寫入操作 jdbcTemplate.update(sql,new Object[]{ employee.getEno() , employee.getEname(),employee.getSalary(),employee.getDname() , employee.getHiredate() }); } public int update(Employee employee){ String sql = "UPDATE employee SET ename = ?, salary = ?, dname = ?, hiredate = ? WHERE eno = ?"; int count = jdbcTemplate.update(sql, new Object[]{employee.getEname(), employee.getSalary(), employee.getDname(), employee.getHiredate(), employee.getEno()}); return count; } public int delete(Integer eno){ String sql = "delete from employee where eno = ?"; return jdbcTemplate.update(sql, new Object[]{eno}); } public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } }
BatchService.java
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Date; @Service @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true) public class BatchService { @Resource private EmployeeDao employeeDao; @Transactional(propagation = Propagation.REQUIRES_NEW) public void importJob1(){ for (int i = 1; i <= 10; i++) { Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("研發部員工" + i); employee.setSalary(4000f); employee.setDname("研發部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void importJob2(){ for (int i = 1; i <= 10; i++) { Employee employee = new Employee(); employee.setEno(9000 + i); employee.setEname("市場部員工" + i); employee.setSalary(4500f); employee.setDname("市場部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
EmployeeService.java
package com.imooc.spring.jdbc.service; import com.imooc.spring.jdbc.dao.EmployeeDao; import com.imooc.spring.jdbc.entity.Employee; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.Date; @Service //宣告式事務核心註解 //放在類上,將宣告式事務配置應用於當前類所有方法,預設事務傳播為REQUIRED @Transactional public class EmployeeService { @Resource private EmployeeDao employeeDao; @Resource private BatchService batchService; @Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true) public Employee findById(Integer eno){ return employeeDao.findById(eno); } public void batchImport() { for (int i = 1; i <= 10; i++) { /*if(i==3){ throw new RuntimeException("意料之外的異常"); }*/ Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("員工" + i); employee.setSalary(4000f); employee.setDname("市場部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public void startImportJob(){ batchService.importJob1(); if(1==1){ throw new RuntimeException("意料之外的異常"); } batchService.importJob2(); System.out.println("批量匯入成功"); } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } public BatchService getBatchService() { return batchService; } public void setBatchService(BatchService batchService) { this.batchService = batchService; } }