jdbcTemplate使用
xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<!-- spring里面自带了一个连接池,, 也可以用druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
java
@Configuration
@PropertySource("classpath:db.properties")
@Data
public class JavaConfig {
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
@Value("${db.url}")
private String url;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUsername(username);
ds.setUrl(url);
ds.setPassword(password);
return ds;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
java
public class Demo {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
JdbcTemplate jdbcTemplate = ctx.getBean(JdbcTemplate.class);
// // ddl 创建一张表,修改一个表的结构 data definition language 表定义操作
// jdbcTemplate.execute();
// // 批处理,调用这个方法
// jdbcTemplate.batchUpdate();
// // 增删改
// jdbcTemplate.update();
// jdbcTemplate.query();
// jdbcTemplate.queryForList();
// ddl data definition language
// jdbcTemplate.execute();
// 批处理
// jdbcTemplate.batchUpdate()
// jdbcTemplate.queryForList()
// jdbcTemplate.query()
// add(jdbcTemplate);
// update(jdbcTemplate);
// delete(jdbcTemplate);
// query2(jdbcTemplate);
// query3(jdbcTemplate);
// query4(jdbcTemplate);
// query5(jdbcTemplate);
query6(jdbcTemplate);
}
private static void add(JdbcTemplate jdbcTemplate){
int row = jdbcTemplate.update("insert into user(name,address) values (?,?)", "cc", "chengdu");
System.out.println("row = " + row);
}
private static void update(JdbcTemplate jdbcTemplate){
int row = jdbcTemplate.update("update user set address = ? where id=? ", "beijing", 1);
System.out.println("row = " + row);
}
private static void delete(JdbcTemplate jdbcTemplate){
int row = jdbcTemplate.update("delete from user where id =?", 4);
System.out.println("row = " + row);
}
public static void query(JdbcTemplate jdbcTemplate){
jdbcTemplate.query("select * from user where address = ?", new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
// 外面声明一个list
}
},"chengdu");
}
public static void query2(JdbcTemplate jdbcTemplate){
List<User> list = jdbcTemplate.query("select * from user where address = ?", new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAddress(rs.getString("address"));
return user;
}
}, "chengdu");
System.out.println(Arrays.toString(list.toArray()));
}
public static void query3(JdbcTemplate jdbcTemplate){
// 自动将查询结果 映射到对象上,,, 前提是查询的结果的列名称必须和实体的属性名称保持一致
// 属性对不上,使用RowMapper自己去做一一映射
List<User> list = jdbcTemplate.query("select * from user where address = ?", new BeanPropertyRowMapper<>(User.class), "chengdu");
System.out.println(Arrays.toString(list.toArray()));
}
public static void query4(JdbcTemplate jdbcTemplate){
// 查询一个用户
User user = jdbcTemplate.queryForObject("select * from user where id = ?", new BeanPropertyRowMapper<>(User.class), 1);
System.out.println("user = " + user);
}
public static void query5(JdbcTemplate jdbcTemplate){
// 第二个参数,,设置普通对象,,,查询结果只能返回一列
String name = jdbcTemplate.queryForObject("select name from user where id = ?", String.class, 1);
System.out.println("name = " + name);
}
public static void query6(JdbcTemplate jdbcTemplate){
// queryForList只能有一列,不能有多列
List<String> users = jdbcTemplate.queryForList("select name from user", String.class);
System.out.println(Arrays.toString(users.toArray()));
}
}
传统的jdbcTemplate都是用 ?
做占位符,, ,,也可以不使用?
而是使用变量名称来传递参数。。使用NamedParameterJdbcTemplate
...避免问号的顺序搞反了,
java
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(){
return new NamedParameterJdbcTemplate(dataSource());
}
java
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
NamedParameterJdbcTemplate jdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
Map<String, Object> map = new HashMap<>();
map.put("id",2);
map.put("address","chengdu");
List<User> list = jdbcTemplate.query("select * from user where id>:id and address = :address", map, new BeanPropertyRowMapper<>(User.class));
System.out.println(Arrays.toString(list.toArray()));
事务
事务的四大特性
acid
- atomicity : 原子性,,, 一个事务中的所有操作,要么全部完成,要么全部不完成
- consistency : 一致性 ,,, 事务开始之前和事务结束之后,数据库的完整性没有被破坏
- isolation : 隔离性,, 多个事务执行,会隔离,,事务隔离等级:读未提交,读已提交,可重复读,串行化
- durability : 持久性,,,事务结束之后,对数据的修改是永久的
spring中的事务
编程式事务:
使用TransactionManager,, 先传入数据源配置一个TransactionManager,
java
/**
* 事务管理器
* @return
*/
@Bean
PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(dataSource());
}
transaction.getTransaction()
获取一个事务,,
transaction.commit()
: 提交事务
transaction.rollback()
: 回滚事务
java
public void transfer(String from,String to,Double money){
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(definition);
try {
accountDao.minusMoney(from,money);
// int i = 1/0;
accountDao.addMoney(to,money);
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
System.out.println(e.getMessage());
// 回滚事务
transactionManager.rollback(status);
}
}
}
也可以用 transactionTemplate ,, spring利用aop的思想,把很多重复的东西封装了,比如jdbcTeamplate,transactionTemplate
java
@Bean
TransactionTemplate transactionTemplate(){
// 传入一个 transactionManager
return new TransactionTemplate(platformTransactionManager());
}
java
public void transfer(String from,String to,Double money){
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
accountDao.minusMoney(from,money);
int i = 1/0;
accountDao.addMoney(to,money);
}
});
}
编程式事务一般不用,因为还是有侵入的代码,事务和业务的代码混在一起
声明式事务
声明式事务本质上是aop,,需要配置tx:advice ,,事务的通知,,也就是需要被增强的方法名字,,,
aop将这个通知配置进去。。。
声明式事务底层就是aop,需要添加包:
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.4</version>
</dependency>
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 http://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/cache http://www.springframework.org/schema/cache/spring-cache.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 base-package="com.cj.ts02"/>
<context:property-placeholder location="db.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 声明式事务,本质还是aop-->
<!-- 要增强的方法-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 配置事务的属性,哪些方法要加事务,可以使用通配符 -->
<tx:method name="transfer"/>
<!-- <tx:method name="add*"/>-->
</tx:attributes>
</tx:advice>
<!-- aop配置,,, 相当于把这个transfer方法动态代理了,,-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.cj.ts02.AccountService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
</beans>
java代码实现事务:使用EnableTransactionManager
开启事务,,开启了之后,,就能直接使用@Transactional
注解去标识,哪个方法要有事务
java
@Configuration
@ComponentScan(basePackages = "com.cj.ts02")
@PropertySource("classpath:db.properties")
// 开启事务注解,, 开了之后只需要在方法上添加 @Transactional 即可
@EnableTransactionManagement
public class JavaConfig {
@Value("${db.url}")
private String url;
@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
@Bean
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
@Bean
DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUsername(username);
ds.setPassword(password);
ds.setUrl(url);
return ds;
}
/**
* 事务管理器
* @return
*/
@Bean
PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(dataSource());
}
}
java和xml结合使用,,, 在xml中可以使用<tx:annotation-driven/>
: 这个配置表示启用事务注解,,相当于@EnableTransactionManager
自定义事务注解
自己定义一个注解,用来回滚事务,,,在切面中使用编程式注解,,,将事务融入到切面中
java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface MyTx {
}
java
@Component
@Aspect
@EnableAspectJAutoProxy
public class MyTxAspect {
@Autowired
PlatformTransactionManager transactionManager;
@Pointcut("@annotation(MyTx) || @within(MyTx)")
public void pc(){}
@Around("pc()")
public Object around(ProceedingJoinPoint pjp){
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(definition);
Object result = null;
try {
result = pjp.proceed();
transactionManager.commit(status);
} catch (Throwable e) {
System.out.println("e.getMessage() = " + e.getMessage());
transactionManager.rollback(status);
// throw new RuntimeException(e);
}
return result;
}
}
spring事务的属性
-
隔离性 : spring中的隔离性和 mysql中的隔离性是差不多的
@Transactional(isolation = Isolation.DEFAULT)
default: 表示mysql使用哪种隔离级别,spring就用哪种
-
传播性 : 一个方法中有事务,,里面又调用了另一个方法的事务,,,两个事务怎么处理。。。。 多个事务怎么处理就是传播性
- REQUIRED : 如果事务存在,不会开启一个新的事务,,加入到之前存在的事务里面,,,,最多只有一个事务
- REQUIRES_NEW: 如果事务存在,会将当前事务挂起,开启一个新的事务
- NESTED : 嵌套事务,, 相当于设置了一个保存点,,事务可以全部回滚,也可以回滚到保存点,,, 嵌套事务,就是在第一个方法结束的时候设置一个保存点,,, 里面的方法报错,会回滚到这个保存点,不会影响外面的方法,,, 如果外面的方法报错,,因为是一个事务,,里面的是不会提交的,所以里面的也会跟着回滚
- MANDATORY : 加在子方法上,,表示外面的方法必须有事务,,没有事务会抛错
- SUPPORTS: 如果存在事务就加入,,如果没有事务,就以非事务的方式去运行
- NOT_SUPPORTED: 就要以非事务的方式进行,,如果存在事务,将当前事务挂起,,执行完成之后再执行事务
- NEVER : 就是不在事务里面运行,如果存在事务,会报错
事务回滚机制
不是所有异常都会回滚,,默认是运行时异常才会回滚。。,, @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
设置rollbackFor ,指定什么异常回滚,, 设置noRollbackFor,指定什么异常不回滚
是否只读
只读事务一般设置在查询方法上,但不是所有的查询方法都需要只读事务。。。如果一个业务中有多个查询sql,,每个查询sql都会开启一个独立的事务,,如果有并发操作修改了数据,,那么每个sql查询出来的结果可能会不一样,,
开启只读事务就是将多个查询sql放入到一个事务中,多条相同的sql在该事务中执行将会获取到相同的查询结果
java
@Transactional(propagation = Propagation.REQUIRED,readOnly = true)
超时时间
有的时候发生死锁,不可能一直在那等着,,
java
@Transactional(propagation = Propagation.REQUIRED,timeout = 10)
事务失效
声明式事务失效,,aop什么时候失效,,,,aop需要生成子类,,不能用final修饰
- 方法自调用失效
事务调用的是代理对象中的方法,,,如果方法自调用,,调的是this自己的方法,不是代理对象的方法
解决: 自己注入自己,,然后调用 - 异常被捕获
- 方法是非public, 被aop代理,aop需要生成子类,private私有方法,不能重写
- 非运行时异常 : 默认捕获运行时异常,,
- 不是spring bean,,, 对象必须是spring创建的
- 数据库不支持事务
在spring中加事务的方式都一样,,,spring中将事务的实现都抽象出来了,有一套抽象的解决方案,,,框架只需要实现接口,就能使用spring中的事务
TransactionManager : 事务管理器
TransactionDefinition : 事务的定义
TransactionStatus : 事务的状态
PlatformTransactionManager
- getTransaction()