当前位置: 首页 > news >正文

Spring JDBC和事务控制

目录

    • Spring JDBC 和 事务控制主要内容
    • Spring 整合 JDBC 环境
      • 构建项目添加依赖坐标
      • 添加 jdbc 配置文件
      • 编写 spring 配置文件
      • 配置数据源
        • C3P0 数据源配置
        • DBCP 数据源配置
      • 模板类配置
      • Spring JDBC 测试 (入门)
        • 创建指定数据库
        • 创建数据表
        • 使用 JUnit 测试
          • JUnit 测试
          • 优化JUnit单元测试-传统单元测试方式
          • 优化JUnit单元测试-Spring Test 模块
            • 添加依赖
            • 代码中加入注解
          • 优化JUnit单元测试-Spring Test 模块-通用封装
    • 持久层(dao层)- 账户模块 - 操作
      • 定义实体类
      • dao层中账户接口及方法定义
        • 定义接口类
        • 定义接口实现类
      • 账户记录添加实现
        • 添加账户记录
        • 添加记录返回主键
        • 批量添加账户记录
      • 账户记录查询实现
        • 查询用户的账户总记录数
        • 查询指定账户记录详情
        • 多条件查询用户账户记录
      • 账户记录更新实现
        • 更新账户记录
        • 批量更新账户记录
      • 账户记录删除实现
        • 删除账户记录
        • 批量删除账户记录
    • Spring 事务控制
      • 转账场景模拟实现
        • 接口方法定义
        • 实现对应接口
        • 转账方法业务层实现
        • 需加入事务控制
      • Spring 事务核心接口
          • JDBC 事务
          • Hibernate 事务
          • Java 持久化 API 事务(JPA)
          • Java 原生 API 事务
      • Spring 事务控制配置
        • XML 配置
          • 导入命名空间依赖
          • 添加命名空间
        • 声明式事务属性介绍
          • name属性
          • readonly属性(true|false)
          • timeout
          • rollback-for属性
          • propagation属性
          • isolation属性
        • 注解配置
          • 配置事务管理器
          • 加入配置注解扫描
          • 方法上加入事务注解

Spring JDBC 和 事务控制主要内容

(img-5c8BLbvZ-1682531464765)(03-Spring JDBC和事务控制.assets/SpringJDBC-19.png)]

Spring 整合 JDBC 环境

​ Spring 框架除了提供 IOC 与 AOP 核心功能外,同样提供了基于JDBC 的数据访问功能,使得访问持久层数据更加方便。想要使用 Spring JDBC 环境,需要将JDBC整合到Spring中。

构建项目添加依赖坐标

构建项目:普通的java项目即可

<!-- 添加相关的依赖坐标 -->
<!-- spring 依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.5</version>
</dependency>
<!-- spring jdbc -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>6.0.5</version>
</dependency>
<!-- 用mysql数据库,就需要 mysql 驱动包 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.19</version>
</dependency>
<!-- c3p0 连接池
初识为什么要用连接池:
对于一个简单的数据库引用,如果对数据库的访问不是很频繁,这种情况可以简单的在需要访问数据库时,创建一个链接,用完关闭它,这样做不会有太明显的性能上的开销。但是对于复杂的数据库引用,情况就截然不同了,频繁的建立、关闭连接,会极大的减低系统的性能,这是对于连接的使用造成系统性能的瓶颈。连接池的作用:
连接池的作用是为了提高性能,避免重复多次的打开数据库连接而造成性能的下降和系统资源的浪费;连接池是将已经创建好的连接保存在池中,当有请求来时,直接使用已经创建好的连接对数据库进行访问。这样省略了创建和销毁的过程。这样以提高系统的性能。
-->
<dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.5</version>
</dependency>

添加 jdbc 配置文件

在src/main/resources目录下新建db.properties配置文件,并设置对应的配置信息

放在配置文件的目的:后续修改的时候直接到配置文件中修改即可。

# 驱动名
jdbc.driver=com.mysql.cj.jdbc.Driver
# 数据库连接
jdbc.url=jdbc:mysql://127.0.0.1:3306/(数据库名称)?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
# 数据库用户名称
jdbc.user=(数据库账号)
# 数据库用户密码
jdbc.password=(数据库密码)

以下为可选配置

# 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3个,可以自己设置为20个:
initialPoolSize=20
# 指定连接池中保留的最大连接数. Default:15个,可以自己设置为100个:
maxPoolSize=100
# 指定连接池中保留的最小连接数
minPoolSize=10
# 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0秒,可以自己设置为60秒:
maxIdleTime=60
# 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数. Default:3个,可以自己设置为5个:
acquireIncrement=5
# JDBC的标准,用以控制数据源内加载的PreparedStatements数量。
maxStatements=5
# 每60秒检查所有连接池中的空闲连接,因为它本身是有回收机制的,Default:0,可以自己设置为60:
idleConnectionTestPeriod=60

编写 spring 配置文件

编写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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!--加载db.properties属性文件,用来读取db.properties中的数据--><context:property-placeholder location="db.properties"></context:property-placeholder></beans>

配置数据源

​ 由于建立数据库连接是一个非常耗时耗资源的行为,所以通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。

​ 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 数据源配置

在applicationContext.xml文件中加入如下配置:

<!-- 配置 c3p0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><!-- property标签的value属性对应的是jdbc.properties中的值 --><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property>
</bean>

C3P0 其他额外配置(对应的值在jdbc.properties文件中指定)

<!-- 指定连接池中保留的最大连接数。 Default:15-->
<property name="maxPoolSize" value="${maxPoolSize}"/>
<!-- 指定连接池中保留的最小连接数。-->
<property name="minPoolSize" value="${minPoolSize}"/>
<!-- 指定连接池的初始化连接数。取值应在minPoolSize 与 maxPoolSize 之间.Default:3-->
<property name="initialPoolSize" value="${initialPoolSize}"/>
<!-- 最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。 Default:0-->
<property name="maxIdleTime" value="${maxIdleTime}"/>
<!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。 Default:3-->
<property name="acquireIncrement" value="${acquireIncrement}"/>
<!-- JDBC的标准,用以控制数据源内加载的PreparedStatements数量。  
但由于预缓存的statements属于单个connection,而不是整个连接池所以设置这个参数需要考虑到多方面的因数。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:0-->
<property name="maxStatements" value="${maxStatements}"/>
<!-- 每60秒检查所有连接池中的空闲连接。Default:0 -->
<property name="idleConnectionTestPeriod" value="${idleConnectionTestPeriod}"/>

DBCP 数据源配置

在applicationContext.xml文件中加入如下配置:

<!-- 配置dbcp数据源-->
<bean id="myDataSource" class="org.apache.commons.dbcp2.BasicDataSource"><property name="driverClassName" value="${jdbc.driver}" /><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.user}"/><property name="password" value="${jdbc.password}"/><!-- 连接池启动时的初始值 -->  <property name="initialSize" value="1"/>  <!-- 最大空闲值.当经过一个高峰时间后,连接池可以将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->  <property name="maxIdle" value="2"/>  <!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请一些连接,以避免洪峰来时再申请而造成的性能开销 -->  <property name="minIdle" value="1"/>  
</bean>

模板类配置

​ 创建JDBC的模板,我们会通过模板进行JDBC相关的操作。

​ Spring把 JDBC 中重复的操作建立成了一个模板类:org.springframework.jdbc.core.JdbcTemplate 。

<!-- 配置JdbcTemplate模板实例,并注入一个dataSource数据源-->
<bean id="jdbcTemplate"  class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property>
</bean>

Spring JDBC 测试 (入门)

创建指定数据库

选择连接,右键选择"新建数据库",设置数据库的名称和编码格式

在这里插入图片描述

创建数据表

创建账户表:tb_account:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3lGPLbL5-1682531464768)(03-Spring JDBC和事务控制.assets/SpringJDBC-02.png)]

创建表以后表中加入数据:
在这里插入图片描述

使用 JUnit 测试

进行 junit 测试,解析applicationContext.xml,获取 jdbcTemplate bean

如果使用junit,需要将junit依赖加入到pom.xml中:

<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope><!--只能在test目录下进行单元测试,在main->java目录下就无法使用-->
</dependency>
JUnit 测试

在test/java目录下新建com.msb.test包,构建测试类和方法:

package com.msb.test;import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;/*** @Author: zhaoss*/
public class SpringJdbcTest01 {@Testpublic void testQueryCount() {// 获取spring上下文环境ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");// 得到模板类 JdbcTemplate对象JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");// 通过模板进行CRUD操作// 定义sql语句:案例:查询总计有多少个账户String sql = "select count(1) from tb_account";// 执行查询操作(无参数)Integer total= jdbcTemplate.queryForObject(sql, Integer.class);System.out.println("总记录数:" + total);}@Testpublic void testQueryCountByUserId() {// 获取spring上下文环境ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");// 得到模板类 JdbcTemplate对象JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");// 通过模板进行CRUD操作// 定义sql语句:案例:查询指定用户有多少个账户String sql = " select count(1) from tb_account where user_id = ?";// 执行查询操作(有参数)第一个参数Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);System.out.println("总记录数:" + total);}
}

JdbcTemplate模板如何使用呢,可以通过官网查看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EBAqZVNu-1682531464769)(03-Spring JDBC和事务控制.assets/SpringJDBC-04.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BlFdDBLD-1682531464770)(03-Spring JDBC和事务控制.assets/SpringJDBC-05.png)]

优化JUnit单元测试-传统单元测试方式

在上面入门测试案例中,可以看到:testQueryCount()方法和testQueryCountByUserId()方法有很多重复的东西,那么重复的东西能否做到简单的封装呢?基于单元测试的特性,可以使用 @Before注解:(在执行单元测试方法之前可以执行的代码)

package com.msb.test;import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;/*** @Author: zhaoss*/
public class SpringJdbcTest01 {private JdbcTemplate jdbcTemplate;@Beforepublic void init() {// 获取spring上下文环境ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");// 得到模板类 JdbcTemplate对象jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");}@Testpublic void testQueryCount() {// 通过模板进行CRUD操作// 定义sql语句:案例:查询总计有多少个账户String sql = "select count(1) from tb_account";// 执行查询操作(无参数)Integer total= jdbcTemplate.queryForObject(sql, Integer.class);System.out.println("总记录数:" + total);}@Testpublic void testQueryCountByUserId() {// 通过模板进行CRUD操作// 定义sql语句:案例:查询指定用户有多少个账户String sql = " select count(1) from tb_account where user_id = ?";// 执行查询操作(有参数)第一个参数Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);System.out.println("总记录数:" + total);}
}
优化JUnit单元测试-Spring Test 模块

Spring可以让单元测试用起来更加简单,Spring提供了Spring Test 模块,该模块整合了一些常见的单元测试工具,例如Junit。

整合后可以在测试类中直接使用Spring容器中的内容,相当于把测试类也放入到Spring容器中,测试类里面可以直接使用注解注入一些bean对象。

同时也可以通过@ContextConfigration注解指定配置文件路径,让测试方法在启动的时候直接加载配置文件。

添加依赖

​ spring-context核心依赖无论使用哪个模块都必须导入的。(这个我们已经添加过了)

​ 需要导入junit依赖的,现在使用的就是Spring Test模块整合的junit。(这个我们已经添加过了)

​ spring-test 表示spring test模块的依赖,导入后才能有@ContextConfiguration这些注解 (这个需要现在添加)

<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>6.0.5</version><scope>test</scope>
</dependency>
代码中加入注解
package com.msb.test;import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;/*** @Author: zhaoss*/
// 使用Spring整合Junit4的类启动当前测试类 (这个注解必须要用4.12以上版本的junit)
// RunWith、ContextConfiguration这些注解不用扫描,@RunWith就会自动去构建测试类对象
@RunWith(SpringJUnit4ClassRunner.class)
// 启动时加载的配置文件,里面要包含classpath
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringJdbcTest02 {// 将容器中的jdbcTemplate注入进来@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void testQueryCount() {// 通过模板进行CRUD操作// 定义sql语句:案例:查询总计有多少个账户String sql = "select count(1) from tb_account";// 执行查询操作(无参数)Integer total= jdbcTemplate.queryForObject(sql, Integer.class);System.out.println("总记录数:" + total);}@Testpublic void testQueryCountByUserId() {// 通过模板进行CRUD操作// 定义sql语句:案例:查询指定用户有多少个账户String sql = " select count(1) from tb_account where user_id = ?";// 执行查询操作(有参数)第一个参数Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);System.out.println("总记录数:" + total);}
}

PS:上面的注解生效,并没有在applicationContext.xml中配置扫描,因为Spring整合Junit4的类会帮我们处理,无需我们考虑扫描问题。

优化JUnit单元测试-Spring Test 模块-通用封装

如果有n多个测试类,每次都要写@RunWith和@ContextConfiguration注释,如果配置文件修改,那么每个文件中的classpath都要修改,很麻烦,所以我们可以再次提取:

  1. 定义一个父类,设置通用的配置信息

    /*** @Author: zhaoss*/
    // 使用Spring整合Junit4的类启动当前测试类 (这个注解必须要用4.12以上版本的junit)
    // RunWith、ContextConfiguration这些注解不用扫描,@RunWith就会自动去构建测试类对象
    @RunWith(SpringJUnit4ClassRunner.class)
    // 启动时加载的配置文件,里面要包含classpath
    @ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class BaseTest {}
    
  2. 继承通用的测试类

    public class SpringJdbcTest03 extends BaseTest {// 将容器中的jdbcTemplate注入进来@Autowiredprivate JdbcTemplate jdbcTemplate;@Testpublic void testQueryCount() {// 通过模板进行CRUD操作// 定义sql语句:案例:查询总计有多少个账户String sql = "select count(1) from tb_account";// 执行查询操作(无参数)Integer total= jdbcTemplate.queryForObject(sql, Integer.class);System.out.println("总记录数:" + total);}@Testpublic void testQueryCountByUserId() {// 通过模板进行CRUD操作// 定义sql语句:案例:查询指定用户有多少个账户String sql = " select count(1) from tb_account where user_id = ?";// 执行查询操作(有参数)第一个参数Integer total = jdbcTemplate.queryForObject(sql, Integer.class, 1);System.out.println("总记录数:" + total);}     
    }
    

持久层(dao层)- 账户模块 - 操作

​ 当完成 Spring Jdbc 环境集成后,这里使用spring jdbc 完成账户单表tb_account的crud 操作。但是实际开发中还是使用框架来操作,比如Mybatis。

定义实体类

创建com.msb.pojo包,定义Account.java

package com.msb.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() {}// 构造器中不需要加:账户ID(利用主键自增即可)、创建时间、修改时间(这个时间通过sql操作即可)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;}@Overridepublic 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;}
}

dao层中账户接口及方法定义

定义接口类

AccountDao.java

package com.msb.dao;
import com.msb.pojo.Account;
import java.util.List;/*** @Author: zhaoss* 用户模块 接口定义*      1. 添加账户*          添加账户记录,返回受影响的行数*          添加账户记录,返回记录的主键*          批量添加账户记录,返回受影响的行数*      2. 查询账户*          查询指定用户的账户总记录数,返回该记录数*          通过账户主键id,查询指定账户的记录详情,返回账户信息*          多条件查询指定用户的账户列表,返回账户集合*      3. 更新账户*          更新账户记录,返回受影响的行数*          批量更新账户记录,返回受影响的行数*      4. 删除账户*          删除账户记录,返回受影响的行数*          批量删除账户记录,返回受影响的行数*/
public interface AccountDao {// 添加操作:添加账户记录,返回受影响的行数:int addAccount(Account account);// 添加操作:添加账户记录,返回记录的主键:int addAccountReturnKey(Account account);// 添加操作:批量添加账户记录,返回受影响的行数:int addAccountBatch(List<Account> accounts);// 查询操作:查询指定用户的账户总记录数,返回账户总记录数:int queryAccountCount(Integer userId);// 查询操作:通过账户的主键id,查询对应的账户的信息:Account queryAccountById(Integer accountId);/**查询操作:多条件查询指定用户的账户列表,返回账户集合:@param userId 指定的账户的id@param accountName 账户名称 (利用模糊查询)@param createTime 创建时间  (查询大于当前时间)*/List<Account> queryAccountByParams(Integer userId,String accountName,String createTime);// 更新操作:更新账户记录,返回受影响的行数:int updateAccountById(Account account);// 更新操作:批量更新账户记录,返回受影响的行数:int updateAccountBatch(List<Account> accounts);// 删除操作:根据主键id删除对应的账户,返回受影响的行数:int deleteAccountById(Integer accountId);// 删除操作:批量删除-根据主键id删除对应的账户,返回受影响的行数:int deleteAccountBatch(Integer[] accountIds);}

定义接口实现类

package com.msb.dao.impl;import com.msb.dao.AccountDao;
import com.msb.pojo.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import java.util.List;/*** @Author: zhaoss* AccountDaoImpl对象的构建,我们交给ioc容器去管理,加入@Repository注解:*/
@Repository
public class AccountDaoImpl implements AccountDao {// 注入JdbcTemplate模板对象:@AutowiredJdbcTemplate jt;@Overridepublic int addAccount(Account account) {return 0;}@Overridepublic int addAccountReturnKey(Account account) {return 0;}@Overridepublic int addAccountBatch(List<Account> accounts) {return 0;}@Overridepublic int queryAccountCount(Integer userId) {return 0;}@Overridepublic Account queryAccountById(Integer accountId) {return null;}@Overridepublic List<Account> queryAccountByParams(Integer userId, String accountName, String createTime) {return null;}@Overridepublic int updateAccountById(Account account) {return 0;}@Overridepublic int updateAccountBatch(List<Account> accounts) {return 0;}@Overridepublic int deleteAccountById(Integer accountId) {return 0;}@Overridepublic int deleteAccountBatch(Integer[] accountIds) {return 0;}
}

如果想要@Repository注解和 @Autowired注解起作用,必须要在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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:property-placeholder location="db.properties"></context:property-placeholder><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><!--扫描注解--><context:component-scan base-package="com.msb.dao"></context:component-scan>
</beans>

账户记录添加实现

​ 在企业项目开发时,对于记录的添加可能涉及到多种添加方式,比如添加单条记录,批量添加多条记录等情况。这里对于账户记录添加方式分为三种方式:添加单条记录返回受影响行数、添加单条记录返回主键、批量添加多条记录。

添加账户记录

AccountDaoImpl.java中重写方法:

/*** 添加单条记录,返回受影响的行数* @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);
}       

测试方法

package com.msb.test;import com.msb.dao.AccountDao;
import com.msb.pojo.Account;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;/*** @Author: zhaoss*/
public class TestSpringJDBCAddTest extends BaseTest{// 注入持久层对象(dao层)对象@AutowiredAccountDao accountDao;@Testpublic void testAddAccount(){// 准备你要添加的数据:Account account = new Account("账号3","工商银行",400.0,"奖金",1);// 调用AccountDaoImpl对象的addAccount方法,传入上面要添加的数据account:int row = accountDao.addAccount(account);System.out.println("添加账户,受影响的行数为:" + row);}
}

参照:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hZAckNJ5-1682531464771)(03-Spring JDBC和事务控制.assets/SpringJDBC-10.png)]

添加记录返回主键

@Overridepublic int addAccountReturnKey(Account account) {// 添加操作:添加账户记录,返回记录的主键:// 定义sql:String sql = "insert into tb_account " +"(account_name,account_type,money,remark,create_time,update_time,user_id) " +"values (?,?,?,?,now(),now(),?)";// 利用模板调用方法,返回主键:// 定义KeyHolder对象,获取记录的主键值:KeyHolder keyHolder = new GeneratedKeyHolder();// 利用模板调用方法:/*jt.update(new PreparedStatementCreator() {@Overridepublic PreparedStatement createPreparedStatement(Connection con) throws SQLException {// 预编译sql语句,并设置返回主键:PreparedStatement ps = con.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);*/// 利用lambda表达式的写法:jt.update(con ->  {// 预编译sql语句,并设置返回主键:PreparedStatement ps = con.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);// 通过keyHolder对象来获取主键:int key = keyHolder.getKey().intValue();return key;}

测试方法

/*** 添加账户记录,返回主键*/@Testpublic void testAddAccount2(){// 准备你要添加的数据:Account account = new Account("账号4","招商银行",1400.0,"兼职费用",2);// 调用AccountDaoImpl对象的addAccount方法,传入上面要添加的数据account:int key = accountDao.addAccountReturnKey(account);System.out.println("对应的主键为:" + key);}

参照:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGbwOY7y-1682531464772)(03-Spring JDBC和事务控制.assets/SpringJDBC-07.png)]

批量添加账户记录

/*** 添加多条记录,返回受影响的行数* @param accounts* @return*/
@Override
public int addAccountBatch(List<Account> accounts) {// 添加操作:批量添加账户记录,返回受影响的行数:// 定义sql:String sql = "insert into tb_account " +"(account_name,account_type,money,remark,create_time,update_time,user_id) " +"values (?,?,?,?,now(),now(),?)";// 通过模板进行批量增加操作:int[] nums = jt.batchUpdate(sql, new BatchPreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps, int i) throws SQLException {// 必须获取每一条记录,给每一条记录赋值:Account account = accounts.get(i);// 给参数设置值: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());}@Overridepublic int getBatchSize() {// 可以得到一共操作多少条记录return accounts.size();}});// 获取影响几行记录:return nums.length;
}

测试方法

/*** 批量添加数据,返回受影响的行数*/
@Test
public void testAddAccountBatch() {// 准备你要添加的数据:Account account1 = new Account("账号8","招商银行",13450.0,"兼职费用",3);Account account2 = new Account("账号9","工商银行",1670.0,"奖金",3);Account account3 = new Account("账号10","建设银行",7400.0,"工资",3);// 上面的三个账户添加到集合中,以便以后传入批量添加的方法中:List<Account> accouts = new ArrayList<>();accouts.add(account1);accouts.add(account2);accouts.add(account3);// 调用AccountDaoImpl对象的addAccountBatch方法,传入上面要添加的数据accouts:int row = accountDao.addAccountBatch(accouts);System.out.println("添加账户,受影响的行数为:" + row);
}

参考:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKWWcl24-1682531464773)(03-Spring JDBC和事务控制.assets/SpringJDBC-08.png)]

账户记录查询实现

​ 账户记录查询这里提供了三种查询方式,查询指定用户所有账户记录数,查询单条账户记录详情,多条件查询指定用户账户记录。

查询用户的账户总记录数

/*** 查询指定用户的账户总记录数,返回记录数* @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);
}

参考:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYw7TQ8e-1682531464774)(03-Spring JDBC和事务控制.assets/SpringJDBC-09.png)]

查询指定账户记录详情

/*** 查询某个账户记录详情,返回账户对象* @param accountId* @return*/
@Overridepublic Account queryAccountById(Integer accountId) {// 查询操作:通过账户的主键id,查询对应的账户的信息:// 定义sql:String sql = "select * from tb_account where account_id = ?";// 利用模板调用方法操作:/*Account account = jt.queryForObject(sql, new RowMapper<Account>() {@Overridepublic Account mapRow(ResultSet rs, int rowNum) throws SQLException {// 创建一个Account对象,接收数据库中查询的数据:Account a = new Account();a.setAccountId(accountId);a.setAccountName(rs.getString("account_name"));a.setAccountType(rs.getString("account_type"));a.setMoney(rs.getDouble("money"));a.setRemark(rs.getString("remark"));a.setCreateTime(rs.getDate("create_time"));a.setUpdateTime(rs.getDate("update_time"));a.setUserId(rs.getInt("user_id"));return a;}}, accountId);*/// 利用lambda表达式简化:Account account = jt.queryForObject(sql, (ResultSet rs, int rowNum) -> {// 创建一个Account对象,接收数据库中查询的数据:Account a = new Account();a.setAccountId(accountId);a.setAccountName(rs.getString("account_name"));a.setAccountType(rs.getString("account_type"));a.setMoney(rs.getDouble("money"));a.setRemark(rs.getString("remark"));a.setCreateTime(rs.getDate("create_time"));a.setUpdateTime(rs.getDate("update_time"));a.setUserId(rs.getInt("user_id"));return a;}, accountId);return account;}

测试方法

/*** 查询指定账户的记录详情,返回账户对象*/
@Testpublic void testquery02(){// 调用accountDao中的queryAccountById:查询操作:通过账户的主键id,查询对应的账户的信息:Account account = accountDao.queryAccountById(10);System.out.println("该用户的账户信息为:" + account.toString());}

参考:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqzW89ZG-1682531464775)(03-Spring JDBC和事务控制.assets/SpringJDBC-11.png)]

多条件查询用户账户记录

/**查询操作:多条件查询指定用户的账户列表,返回账户集合:@param userId 指定的账户的用户id@param accountName 账户名称 (利用模糊查询)@param createTime 创建时间  (查询大于当前时间)*/@Overridepublic List<Account> queryAccountByParams(Integer userId, String accountName, String createTime) {// 定义sql :String sql = "select * from tb_account where user_id = ?";// 定义参数列表:案例我们不用那么多参数,只用userId先测试,参数列表可以先不定义。// 如果案例中有多个参数,那么就需要定义数组,然后传入可变参数位置:--->直接定义数组方案不可取,因为数组长度不能改变:// 将数组改为集合:// Object[] params = {userId};List params = new ArrayList();params.add(userId);// 根据参数是否为空将sql进行拼接:if(accountName != null && !"".equals(accountName)){// 模糊查询  注意:and前面一定要加空格,否则报错sql += " and account_name like concat('%',?,'%')";params.add(accountName);}if(createTime != null && !"".equals(createTime)){sql += " and create_time < ?";params.add(createTime);}// 将集合转为数组:Object[] objects = params.toArray();// 使用模板:List<Account> accountList = jt.query(sql, (ResultSet rs, int rowNum) -> {// 创建一个Account对象,接收数据库中查询的数据:// 查询出来的每个数据,利用一个Account对象做接收,然后query方法内部会将对象放入List集合中返回Account a = new Account();a.setAccountId(rs.getInt("account_id"));a.setAccountName(rs.getString("account_name"));a.setAccountType(rs.getString("account_type"));a.setMoney(rs.getDouble("money"));a.setRemark(rs.getString("remark"));a.setCreateTime(rs.getDate("create_time"));a.setUpdateTime(rs.getDate("update_time"));a.setUserId(rs.getInt("user_id"));return a;}, objects);return accountList;}

测试方法

@Testpublic void testquery03(){// 调用accountDao中的queryAccountByParams:List<Account> accountList = accountDao.queryAccountByParams(1,null,null);/*System.out.println(accountList.toString());System.out.println(accountList.size());*/List<Account> accountList2 = accountDao.queryAccountByParams(1,"1",null);/*System.out.println(accountList2.toString());System.out.println(accountList2.size());*/List<Account> accountList3 = accountDao.queryAccountByParams(1,null,"2023-3-20");System.out.println(accountList3.toString());System.out.println(accountList3.size());}

参考:如果有参数就传入参数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ot3an8UX-1682531464776)(03-Spring JDBC和事务控制.assets/SpringJDBC-12.png)]

账户记录更新实现

更新账户记录

/*** 更新指定账户记录,返回受影响的行数* @param account* @return*/
@Override
public int updateAccountById(Account account) {String sql = "update tb_account set account_name = ?, account_type = ?, " +" money = ? ,remark = ?,user_id = ? ,update_time = now() " +" where account_id = ? ";Object[] objs = {account.getAccountName(),account.getAccountType(),account.getMoney(), account.getRemark(),account.getUserId(),account.getAccountId()};return jdbcTemplate.update(sql,objs);
}

测试方法

/*** 更新指定账户记录,返回受影响的行数*/
@Test
public void testUpdateAccount(){// 准备要修改的数据Account account = new Account("张三1","建设银行1",500.0,"零花钱加倍",1);account.setAccountId(1);int row = accountDao.updateAccountById(account);System.out.println("修改账户返回受影响的行数:" + row);
}

参考:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oLvkCG4B-1682531464777)(03-Spring JDBC和事务控制.assets/SpringJDBC-13.png)]

批量更新账户记录

/*** 批量新账户记录,返回受影响的行数* @param accounts* @return*/
@Override
public int updateAccountBatch(List<Account> accounts) {// 更新操作:批量更新账户记录,返回受影响的行数:// 定义sql:String sql = "update tb_account set account_name = ?,account_type = ?,money = ?,remark = ?," +"update_time = now(),user_id = ? where account_id = ?";// 通过模板进行批量更新操作:int[] nums = jt.batchUpdate(sql, new BatchPreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps, int i) throws SQLException {// 必须获取每一条记录,给每一条记录赋值:Account account = accounts.get(i);// 给参数设置值: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());ps.setInt(6,account.getAccountId());}@Overridepublic int getBatchSize() {// 可以得到一共操作多少条记录return accounts.size();}});// 获取影响几行记录:return nums.length;
}

测试方法

/*** 批量更新账户记录,返回受影响的行数*/
@Test
public void testupdate02(){// 调用accountDao中的updateAccountBatch// 封装Account对象:Account ac1 = new Account("账户66","农业银行",220.0,"奖金",1);ac1.setAccountId(9);Account ac2 = new Account("账户77","农业银行",240.0,"奖金",1);ac2.setAccountId(10);Account ac3 = new Account("账户88","农业银行",290.0,"奖金",1);ac3.setAccountId(11);// 上面的数据要放入List集合中:List list = new ArrayList();list.add(ac1);list.add(ac2);list.add(ac3);int row = accountDao.updateAccountBatch(list);System.out.println("更新操作影响行数为:" + row);}

参考:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8fofDMa-1682531464778)(03-Spring JDBC和事务控制.assets/SpringJDBC-14.png)]

账户记录删除实现

删除账户记录

/*** 删除账户记录,返回受影响的行数* @param accountId* @return*/
@Override
public Integer deleteAccoutById(Integer accountId) {String sql = "delete from tb_account where account_id= ? ";Object[] objs = {accountId};return jdbcTemplate.update(sql,objs);
}

测试方法

/*** 删除账户记录,返回受影响的行数*/
@Test
public void testDeleteAccount(){// 删除ID为1的账户记录int row = accountDao.deleteAccoutById(1);System.out.println("删除账户记录返回受影响的行数:" + row);
}

批量删除账户记录

 /*** 批量删除账户记录,返回受影响的行数* @param ids* @return*/
@Override
public int deleteAccountBatch(Integer[] ids) {String sql = "delete from tb_account where account_id = ?";int row = jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {@Overridepublic void setValues(PreparedStatement ps, int i) throws SQLException {ps.setInt(1,ids[i]);}@Overridepublic int getBatchSize() {return ids.length;}}).length;return row;
}

测试方法

/*** 批量删除账户记录,返回受影响的行数*/
@Test
public void testDeleteAccountBatch(){// 删除多个id的账户记录Integer[] ids = new Integer[]{2,3};int rows = accountDao.deleteAccountBatch(ids);System.out.println("批量删除账户记录返回受影响的行数:" + rows);
}

Spring 事务控制

通过转账操作案例来引入事务管理。

转账场景模拟实现

项目搭建与SpringJDBC中一致,这里不重复赘述。

接口方法定义

dao层定义接口: 对于转账涉及到双方账户以及对应转账金额,所以有入账和出账两个方法。

package com.msb.dao;/*** @Author: zhaoss*/
public interface AccountDao {/*** 收入* @param tarAid 收入金额的账户ID* @param money 收入金额* @return*/public abstract int inAccount(Integer accountId,Double money);/*** 支出* @param outAid 支出金额的账户ID* @param money  支出金额* @return*/public abstract int outAccount(Integer accountId,Double money);
}

实现对应接口

package com.msb.dao.impl;import com.msb.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;/*** @Author: zhaoss*/
@Repository
public class AccountDaoImpl implements AccountDao {// 注入JdbcTemplate模板对象:@AutowiredJdbcTemplate jt;@Overridepublic int inAccount(Integer accountId, Double money) {// 定义sql:String sql = "update tb_account set money = money + ? where account_id = ?";// 参数:Object[] params = {money,accountId};// 使用模板:int i = jt.update(sql, params);return i;}@Overridepublic int outAccount(Integer accountId, Double money) {// 定义sql:String sql = "update tb_account set money = money - ? where account_id = ?";// 参数:Object[] params = {money,accountId};// 使用模板:int i = jt.update(sql, params);return i;}
}

转账方法业务层实现

service层中实现:

package com.msb.service;import com.msb.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @Author: zhaoss*/
@Service
public class AccountService {@Autowiredprivate AccountDao ad;// 定义业务方法:转账操作:返回值为int类型,在方法内部可以定义一个状态码:成功返回1,失败返回0public int updateAccount(Integer outid,Integer inid,Double money){// 定义状态码:成功1,失败0,初始化状态下 code是0int code = 0;// 账户A(id为6)给账户B(id为10)转100 :// 账户A支出100:int outRow = ad.outAccount(outid, money);// 账户B收入100:int inRow = ad.inAccount(inid, money);// 如果收入和支出两个操作都执行成功的话,表示转账成功:if (outRow == 1 && inRow == 1){code = 1;// 转账成功}// 状态码作为方法的返回值:return code;}
}

需加入事务控制

我们在上述的代码中人为制造错误,使账户A支出成功,但是账户B收入失败:

package com.msb.service;import com.msb.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** @Author: zhaoss*/
@Service
public class AccountService {@Autowiredprivate AccountDao ad;// 定义业务方法:转账操作:返回值为int类型,在方法内部可以定义一个状态码:成功返回1,失败返回0public int updateAccount(Integer outid,Integer inid,Double money){// 定义状态码:成功1,失败0,初始化状态下 code是0int code = 0;// 账户A(id为6)给账户B(id为10)转100 :// 账户A支出100:int outRow = ad.outAccount(outid, money);// 加入异常代码:int a = 1 / 0;// 账户B收入100:int inRow = ad.inAccount(inid, money);// 如果收入和支出两个操作都执行成功的话,表示转账成功:if (outRow == 1 && inRow == 1){code = 1;// 转账成功}// 状态码作为方法的返回值:return code;}
}

再次执行测试类发现,程序报错,账户A损失100,但是账户B并没有加100元操作。

出现上述现象的原因:

默认MySQL的事务是自动提交的,也就是说,当执行一条DML语句,MySQL会立即隐式提交事务,也就是说在执行账户A的转账操作的时候,事务提交,然后出现异常,转账B的操作没有执行

PS:事务:是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失效。

解决:

将转账操作放入同一事务中进行处理。

**方式1:**编程式事务。(事务代码由程序员自己书写)

截取以前代码:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eks3oY5-1682531464779)(03-Spring JDBC和事务控制.assets/SpringJDBC-15.png)]

仔细思考代码会发现,在程序运行中无法保证 service 层业务代码不发生异常,如果通过 jdbc 的方式处理事务,此时需要手动方式控制事务,这样的话凡是涉及到事务控制的业务方法均需要开发人员手动来进行事务处理,无法满足生产的需要。

(在业务层加、在dao层加都不合适,即便加进去了,凡是涉及到事务控制的方法都需要处理,太麻烦了,所以需要方式2)

**方式2:**声明式事务。(事务代码由框架负责,程序员进行声明和简单配置即可。)

Spring就提供了事务的处理方式,将所需内容全部封装,程序员只需要简单的配置即可。封装在哪里呢?如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XtKVv0Z0-1682531464779)(03-Spring JDBC和事务控制.assets/SpringJDBC-17.png)]

怎么去理解呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ji1BFs6F-1682531464780)(03-Spring JDBC和事务控制.assets/SpringJDBC-18.png)]

Spring 事务核心接口

​ Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架的事务来实现。

​ Spring 事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring 为各个平台如 JDBC、Hibernate 等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

public interface PlatformTransactionManager(){ // 由 TransactionDefinition 得到 TransactionStatus 对象 TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; // 提交 void commit(TransactionStatus status) throws TransactionException; // 回滚 void rollback(TransactionStatus status) throws TransactionException; 
}

​ 从这里可知具体的具体的事务管理机制对 Spring 来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以 Spring 事务管理的一个优点就是为不同的事务 API 提供一致的编程模型,如 JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

JDBC 事务

​ 如果应用程序中直接使用 JDBC 来进行持久化,此时使用 DataSourceTransactionManager 来处理事务边界。为了使用DataSourceTransactionManager,需要使用如下的 XML 将其装配到应用程序的上下文定义中:

<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource" />
</bean>

​ 实际上,DataSourceTransactionManager 是通过调用 java.sql.Connection 来管理事务,而后者是通过 DataSource 获取到的。通过调用连接的 commit() 方法来提交事务,同样,事务失败则通过调用 rollback() 方法进行回滚。

Hibernate 事务

​ 如果应用程序的持久化是通过 Hibernate 实现的,那么你需要使用 HibernateTransactionManager。对于 Hibernate3,需要在 Spring 上下文定义中添加如下的声明:

<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory" />
</bean>

​ sessionFactory 属性需要装配一个 Hibernate 的 session 工厂,HibernateTransactionManager 的实现细节是它将事务管理的职责委托给 org.hibernate.Transaction 对象,而后者是从 Hibernate Session 中获取到的。当事务成功完成时,HibernateTransactionManager 将会调用 Transaction 对象的 commit() 方法,反之,将会调用 rollback() 方法。

Java 持久化 API 事务(JPA)

​ Hibernate 多年来一直是 Java 持久化标准,但是现在 Java 持久化 API 作为真正的 Java 持久化标准进入大家的视野。如果你计划使用 JPA 的话,那你需要使用 Spring 的 JpaTransactionManager 来处理事务。你需要在 Spring 中这样配置 JpaTransactionManager:

<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"><property name="sessionFactory" ref="sessionFactory" />
</bean>

​ JpaTransactionManager 只需要装配一个 JPA 实体管理工厂(javax.persistence.EntityManagerFactory 接口的任意实现)。 JpaTransactionManager 将与由工厂所产生的 JPA EntityManager 合作来构建事务。

Java 原生 API 事务

​ 如果应用程序没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),此时需要使用 JtaTransactionManager:

<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"><property name="transactionManagerName" value="java:/TransactionManager" />
</bean>

​ JtaTransactionManager 将事务管理的责任委托给 javax.transaction.UserTransaction 和javax.transaction.TransactionManager 对象,其中事务成功完成通过 UserTransaction.commit() 方法提交,事务失败通过 UserTransaction.rollback() 方法回滚。

Spring 事务控制配置

​ 通过 jdbc 持久化事务,对于事务配置实现由两种方式即:Xml 配置,注解配置。

XML 配置

导入命名空间依赖
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.9.1</version><!--<scope>runtime</scope>-->
</dependency>
添加命名空间

在spring.xml配置文件的添加事务tx和aop的命名空间

事务

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

配置如下

<?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:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd"><context:property-placeholder location="db.properties"></context:property-placeholder><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan><!--配置声明式事务--><!--配置事务管理器:封装事务固定套路代码的类--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><!--注入数据源:原因:因为调用封装的方法,需要用Connection,需要靠数据源获取--><property name="dataSource" ref="dataSource"></property></bean><!--配置事务通知  transaction-manager属性表示这个事务通知是哪个事务管理器管理的--><tx:advice id="t" transaction-manager="transactionManager"><!--具体给哪个方法加入通知呢?把方法写在这updateAccount是一个事务单元注意:该方法出现异常就会触发异常通知实现事务回滚,所以在该方法内部决不能加入try-catch对异常处理--><tx:attributes><tx:method name="updateAccount"/></tx:attributes></tx:advice><!--配置事务切面--><aop:config><!--设置哪个方法需要被声明式事务管理,通过切点定义需要声明式事务管理的方法,但是这里表达式的范围写大一些,不能定位到具体方法,需要配合上面事务通知一起才会生效--><aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/><!--advice-ref指向上面的通知id--><aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor></aop:config>
</beans>

备注:默认 spring 事务只在发生未被捕获的 runtimeexcetpion 时才回滚。

spring aop 异常捕获原理:

被拦截的方法需显式抛出异常,并不能经任何处理,这样aop 代理才能捕获到方法的异常,才能进行回滚,默认情况下 aop 只捕获 runtimeexception 的异常,但可以通过配置来捕获特定的异常并回滚换句话说在 service 的方法中不使用 try catch 或者在 catch 中最后加上 throw new RunTimeexcetpion(),这样程序异常时才能被 aop 捕获进而回滚.

声明式事务属性介绍

<tx:method>标签有下面属性的配置

name属性

​ 配置哪些方法需要有事务控制,支持*通配符

<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><!-- 表示所有以update开头的方法需要进行事务管理 --><tx:method name="update*"/><!-- 表示所有方法需要进行事务管理--><!-- <tx:method name="*"/>--></tx:attributes>
</tx:advice>
readonly属性(true|false)

是否是只读事务.如果为true,告诉数据库此事务为只读事务.底层支持查询的代码逻辑,不走提交事务和回滚事务的代码,会对性能有一定提升,所以只要是查询的方法,建议设置readonly=“true”.如果为false(默认值),事务需要提交的事务.建议新增,删除,修改不设置readonly属性或设置readonly=“false”.

<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><!-- 所有select开头的方法执行查询逻辑 --><tx:method name="select*" read-only="true"></tx:method></tx:attributes>
</tx:advice>
timeout

超过时间事务提交失败

<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><tx:method name="select*" timeout="5"></tx:method></tx:attributes>
</tx:advice>
rollback-for属性

​ 异常类型全限定路径,表示出现什么类型的异常进行数据回滚。

<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><!-- 定义只要方法出现了ArithmeticException类型异常及子类型异常都需要进行回滚 --><tx:method name="select*" rollback-for="java.lang.ArithmeticException"></tx:method></tx:attributes>
</tx:advice>

​ 与之对应的属性:no-rollback-for 出现什么类型的异常不回滚,与rollback-for属性配置一个即可

propagation属性

​ 事务传播行为, 包括:
​ REQUIRED,SUPPORTS,MANDATORY,NEVER
​ REQUIRES_NEW,NOT_SUPPORTED,NESTED

​ 我们一般使用: **REQUIRED:**默认值。如果有事务就用,如果没有事务则新增事务。

<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><tx:method name="select*" propagation="REQUIRED"></tx:method></tx:attributes>
</tx:advice>
isolation属性

该属性控制事务的隔离级别,默认值DEFAULT,表示用数据库的隔离级别(MySQL8默认的事务隔离级别REPEATABLE_READ)

注解配置

之前在利用xml配置声明式事务的时候,其实就是完成了一件事:给方法加入事务,那么这么简单的操作用了那么复杂的xml没必要,所以可以通过注解来简化配置。

配置事务管理器

在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:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd"><context:property-placeholder location="db.properties"></context:property-placeholder><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan><!--配置声明式事务--><!--配置事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!--配置事务通知--><!--<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><tx:method name="update*" timeout="5" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRED" isolation="DEFAULT"/></tx:attributes></tx:advice>--><!--配置事务切面--><!--<aop:config><aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/><aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor></aop:config>-->
</beans>
加入配置注解扫描
<?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:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd"><context:property-placeholder location="db.properties"></context:property-placeholder><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="driverClass" value="${jdbc.driver}"></property><property name="jdbcUrl" value="${jdbc.url}"></property><property name="user" value="${jdbc.user}"></property><property name="password" value="${jdbc.password}"></property></bean><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property></bean><context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan><!--配置声明式事务--><!--配置事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!--配置事务通知--><!--<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><tx:method name="update*" timeout="5" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRED" isolation="DEFAULT"/></tx:attributes></tx:advice>--><!--配置事务切面--><!--<aop:config><aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/><aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor></aop:config>--><!--加入配置注解扫描--><tx:annotation-driven />
</beans>
方法上加入事务注解

Service 方法上在需要添加事务的方法上加入事务注解,如需配置属性在参数位置设置即可

    @Transactional(propagation=Propagation.REQUIRED)public int updateAccount(Integer outid,Integer inid,Double money){// 定义状态码:成功1,失败0,初始化状态下 code是0int code = 0;// 账户A(id为6)给账户B(id为10)转100 :// 账户A支出100:int outRow = ad.outAccount(outid, money);// 制造异常://int a = 1 / 0;// 账户B收入100:int inRow = ad.inAccount(inid, money);// 如果收入和支出两个操作都执行成功的话,表示转账成功:if (outRow == 1 && inRow == 1){code = 1;// 转账成功}// 状态码作为方法的返回值:return code;}

PS:如果很多方法都需要事务处理,那么可以直接将这个注解放在类上,代表这个类的所有方法都被事务管控。

y>


<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"></property>
</bean><context:component-scan base-package="com.msb.dao,com.msb.service"></context:component-scan><!--配置声明式事务-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务通知-->
<!--<tx:advice id="t" transaction-manager="transactionManager"><tx:attributes><tx:method name="update*" timeout="5" no-rollback-for="java.lang.ArithmeticException" propagation="REQUIRED" isolation="DEFAULT"/></tx:attributes>
</tx:advice>--><!--配置事务切面-->
<!--<aop:config><aop:pointcut id="pt" expression="execution(* com.msb.service.*.*(..))"/><aop:advisor advice-ref="t" pointcut-ref="pt"></aop:advisor>
</aop:config>--><!--加入配置注解扫描-->
<tx:annotation-driven />

##### 方法上加入事务注解Service 方法上在需要添加事务的方法上加入事务注解,如需配置属性在参数位置设置即可```java@Transactional(propagation=Propagation.REQUIRED)public int updateAccount(Integer outid,Integer inid,Double money){// 定义状态码:成功1,失败0,初始化状态下 code是0int code = 0;// 账户A(id为6)给账户B(id为10)转100 :// 账户A支出100:int outRow = ad.outAccount(outid, money);// 制造异常://int a = 1 / 0;// 账户B收入100:int inRow = ad.inAccount(inid, money);// 如果收入和支出两个操作都执行成功的话,表示转账成功:if (outRow == 1 && inRow == 1){code = 1;// 转账成功}// 状态码作为方法的返回值:return code;}

PS:如果很多方法都需要事务处理,那么可以直接将这个注解放在类上,代表这个类的所有方法都被事务管控。

相关文章:

Spring JDBC和事务控制

目录 Spring JDBC 和 事务控制主要内容Spring 整合 JDBC 环境构建项目添加依赖坐标添加 jdbc 配置文件编写 spring 配置文件配置数据源C3P0 数据源配置DBCP 数据源配置 模板类配置Spring JDBC 测试 &#xff08;入门&#xff09;创建指定数据库创建数据表使用 JUnit 测试JUnit …...

【音视频第16天】详解STUN协议

一个webRTC传输协议搞得自己云里雾里的。现在主动攻克一下。先看看STUN协议。好&#xff0c;我们开始吧 目录 1.讲讲什么是NAT&#xff1f;2.NAT有啥问题&#xff1f;3.四种NAT类型4.STUN Server5.TURN ServerSTUN和TURN的实现&#xff1a;什么是STUN&#xff1f;为什么需要ST…...

C++算法初级11——01背包问题(动态规划2)

C算法初级11——01背包问题&#xff08;动态规划2&#xff09; 文章目录 C算法初级11——01背包问题&#xff08;动态规划2&#xff09;问题引入0-1背包问题分析0-1背包问题的形式化分析优化 问题引入 辰辰采药 辰辰是个天资聪颖的孩子&#xff0c;他的梦想是成为世界上最伟大…...

Python 库大全(下)

格式化输出 模块 reprlib 提供了一份定制的 repr()&#xff0c;用于简洁 地展示各种大的或者多层嵌套的容器变量&#xff1a; >>> import reprlib >>> reprlib.repr(set(\supercalifragilisticexpialidocious\)) "{\a\, \c\, \d\, \e\, \f\, \g\, ...…...

如何用链表实现LRU缓存淘汰算法

链表学习 一、 缓存1.1缓存介绍1.2 缓存策略 二、链表结构2.1 单链表2.2 循环链表2.3 双向链表2.4 双向循环链表2.5 链表与数组性能对比 三、如何基于链表实现LRU缓存淘汰算法 一、 缓存 1.1缓存介绍 缓存是一种提高数据读取性能的技术&#xff0c;在硬件设计、软件开发中都有…...

【01】数据结构与算法基础-数据、数据元素、数据项和数据对象 | 数据类型和抽象数据类型 | 抽象数据类型的表示和C++实现

目录) 0.数据结构的研究内容1.数据、数据元素、数据项和数据对象1.1数据1.2数据元素(Data element)和数据项1.3数据项1.4数据对象1.5数据结构(Data Structure)1.6逻辑结构的种类1.7存储结构的种类2.数据类型和抽象数据类型2.1数据类型(Data Type)2.2抽象数据类型(Abstract …...

PHP匿名类的使用场景有哪些?PHP匿名类怎么用?有什么好处?PHP匿名类如何在运行时动态生成?

以下是一些使用匿名类的场景&#xff1a; 2. 简单的工厂模式&#xff1a;当需要在运行时动态创建一些简单的对象时&#xff0c;可以使用匿名类替代创建不必要的类定义和文件。 function createGreeter($name) {return new class($name) {private $name;public function __cons…...

【并发基础】一篇文章带你彻底搞懂Java线程中断的底层原理——interrupt()、interrupted()、isInterrupted()

目录 〇、Java线程中断与阻塞的区别 0.1 线程中断 0.2 线程阻塞 一、线程的中断 二、中断方法 2.1 void interrupt() 2.1.1 可中断的阻塞 2.1.2 不可中断的阻塞 2.1.3 实践案例 2.2 boolean isInterrupted() 2.3 boolean interrupted() 2.4 代码案例 三、源码分析…...

【c语言】函数的数据传递原理 | 数组传入函数方法

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 给大家跳段街舞感谢支持&#xff01;ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…...

vue3源码(3)——computed

Vue3中的computed底层源码主要是通过使用Proxy对象来实现的。下面是对Vue3中computed底层源码的详细解读&#xff1a; 在Vue3中&#xff0c;computed的实现是通过使用createComputed函数来创建的。createComputed函数接收两个参数&#xff1a;getter和setter。 在createComput…...

数学建模第七天:数学建模算法篇之插值及MATLAB实现

目录 一、前言 1、引例 2、拟合定义 3、拟合与插值的关系 二、拟合 1、线性最小二乘法求解 ①思路 ②解法 2、MATLAB对线性最小二乘拟合的实现 ①函数说明 ②求解例题 3、MATLAB实现非线性曲线拟合 ①lsqcurvefit函数 ②代码求解 4、MATLAB实现非线性最小二乘拟…...

RUST 每日一省:生命周期作用域

生命周期 一个变量的生命周期就是它从创建到销毁的整个过程。 作用域 我们声明的每个变量都有作用域。作用域其实是变量和值存在的环境。作用域是由一对花括号表示的。例如&#xff0c;使用块表达式会创建一个作用域&#xff0c;即任何以花括号开头和结尾的表达式。此…...

【过程8】——能量守恒视角总结感受

一、背景 另一个角度的看到&#xff0c;观望着过程中自己曾经类似的经历(小舅子的工作)。 时间久了&#xff0c;经历多了&#xff0c;感悟会更加的充实&#xff1b;最近自己对于人在维持能量的过程中也有很多的感悟&#xff0c;一并做一下总结 二、过程 1.人为什么天性不愿意…...

kong(4):限流配置

Kong 提供了 Rate Limiting 插件&#xff0c;实现对请求的限流功能&#xff0c;避免过大的请求量过大&#xff0c;将后端服务打挂。 Rate Limiting 支持秒/分/小时/日/月/年多种时间维度的限流&#xff0c;并且可以组合使用。例如说&#xff1a;限制每秒最 多 100 次请求&…...

人脸识别 Face Recognition 入门

人脸识别 Face Recognition 入门概述 总述传统特征方法深度学习方法损失函数改进基于欧几里德和距离的损失基于角度/余弦边距的损失SoftMax 损失及其变体 一级标题二级标题二级标题二级标题 找论文搭配 Sci-Hub 食用更佳 &#x1f4aa; Sci-Hub 实时更新 : https://tool.yovisu…...

【AI绘画】Midjourney的使用及程序示例

Midjourney 1.背景2.Midjourney的原理3.Midjourney的使用方法4.Midjourney的示例代码 1.背景 Midjourney 是一款基于深度学习的图像转换工具&#xff0c;其可以将一张图像转换成具有不同风格的图像&#xff0c;例如将一张照片转换成卡通风格的图像。Midjourney 基于 TensorFlow…...

无公网IP?教你在外远程访问本地wamp服务器「内网穿透」

目录 前言 1.Wamp服务器搭建 1.1 Wamp下载和安装 1.2 Wamp网页测试 2. Cpolar内网穿透的安装和注册 2.1 本地网页发布 2.2 Cpolar云端设置 2.3 Cpolar本地设置 3. 公网访问测试 4. 结语 前言 软件技术的发展日新月异&#xff0c;各种能方便我们生活、工作和娱乐的新…...

leetcode 628. 三个数的最大乘积

题目描述解题思路执行结果 leetcode 628. 三个数的最大乘积 题目描述 三个数的最大乘积 给你一个整型数组 nums &#xff0c;在数组中找出由三个数组成的最大乘积&#xff0c;并输出这个乘积。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;6 示例 2&…...

fork函数如何创建进程,exit/_exit函数如何使进程终止的详细分析与代码实现

&#x1f38a;【进程通信与并发】专题正在持续更新中&#xff0c;进程&#xff0c;线程&#xff0c;IPC&#xff0c;线程池等的创建原理与运用✨&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列…...

重置电脑时提示“缺少所需的驱动器分区”怎么办?

当您启动Windows 10电脑并收到“您的电脑/设备需修复”这个消息提示时&#xff0c;您会马上尝试修复电脑&#xff0c;如果您这样做了&#xff0c;您可能会收到一个“安装Windows的驱动器已被锁定”的信息。如果您尝试重置您的电脑&#xff0c;您可能会收到一条提示&#xff0c;…...

在KylinV10安装Dm8

前言 因为近期&#xff0c;业外和几个朋友想搞点有趣的项目玩玩&#xff0c;既然不以盈利为主&#xff0c;就> 主推国产化&#xff0c;所以这篇记录一下&#xff0c;我在KylinV10安装dm8.最近真的很忙&#xff0c;要负责专研一下国产化工具开发的事&#xff0c;还要负责tb级…...

「SQL面试题库」 No_46 交换工资

&#x1f345; 1、专栏介绍 「SQL面试题库」是由 不是西红柿 发起&#xff0c;全员免费参与的SQL学习活动。我每天发布1道SQL面试真题&#xff0c;从简单到困难&#xff0c;涵盖所有SQL知识点&#xff0c;我敢保证只要做完这100道题&#xff0c;不仅能轻松搞定面试&#xff0…...

SLAM论文速递【SLAM—— RDS-SLAM:基于语义分割方法的实时动态SLAM—4.24(1)

论文信息 题目&#xff1a; RDS-SLAM:Real-Time Dynamic SLAM Using Semantic Segmentation Methods RDS-SLAM:基于语义分割方法的实时动态SLAM论文地址&#xff1a; https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber9318990发表期刊&#xff1a; IEEE Access ( Volum…...

OJ练习第82题——填充书架

填充书架 力扣链接&#xff1a;1105. 填充书架 题目描述 给定一个数组 books &#xff0c;其中 books[i] [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth 。 按顺序 将这些书摆放到总宽度为 shelfWidth 的书架上。 先选几本书放在书架…...

OHOS IDE和SDK的安装方法

参照OpenHarmony应用开发环境安装流程&#xff0c;下载安装OHOS的IDE&#xff0c;过程中需要全程联网。 IDE&#xff0c;安装至D:\Tools\Huawei\DevEcoStudio。 IDE安装成功之后&#xff0c;按照提示下载安装HOS和OHOS的SDK。 nodejs&#xff0c;安装至D:\Tools\Huawei\nodejs…...

New Year Garland(计数类DP)

New Year Garland 题意 ​ 用m种颜色的球装饰n层的圣诞树&#xff0c;圣诞树的第i层由 l i l_{i} li​个彩球串成&#xff0c;且同一层相邻的球颜色不同&#xff0c;相邻的层之间彩球颜色的集合不同&#xff0c;问有多少种方案&#xff0c;对p取模。 分析 ​ 首先先计算每一…...

32岁阿里P7,把简历改成不知名小公司,学历改成普通本科,工作内容不变,投简历全挂!...

hr靠什么来招人&#xff1f; 一位猎头讲述了自己和朋友打赌的故事&#xff1a; 朋友在阿里云&#xff0c;32岁&#xff0c;P7&#xff0c;他把简历上的公司改成不知名&#xff0c;学历改成普通本科&#xff0c;工作内容不变&#xff0c;结果投其他公司&#xff08;比如京东&…...

从三室心脏MRI影像检测主动脉瓣病变

Detecting Aortic Valve Pathology from the 3-Chamber Cine Cardiac MRI View 摘要 背景 心脏磁共振(CMR)是量化心脏容量、功能和血流量的金标准。定制的MR脉冲序列定义了对比机制&#xff0c;采集几何形状和定时&#xff0c;可以在CMR期间应用&#xff0c;以实现独特的组织…...

【JavaWeb】JavaScript

1、JavaScript 介绍 Javascript 语言诞生主要是完成页面的数据验证。因此它运行在客户端&#xff0c;需要运行浏览器来解析执行 JavaScript 代码。 JS 是 Netscape 网景公司的产品&#xff0c;最早取名为 LiveScript;为了吸引更多 java 程序员。更名为 JavaScript。 JS 是弱…...

Apache Doris 1.2.4 Release 版本正式发布|版本通告

亲爱的社区小伙伴们&#xff0c;我们很高兴地宣布&#xff0c;Apache Doris 于 2023 年 4 月 27 日迎来 1.2.4 Release 版本的正式发布&#xff01;在 1.2.4 版本中&#xff0c;Doris 团队已经修复了自 1.2.3 版本发布以来近 150 个问题或性能改进项。同时&#xff0c;1.2.4 版…...