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

SpringBoot整合数据库连接

JDBC

1、SQL准备

DROP TABLE IF EXISTS `t_book`;CREATE TABLE `t_book` (`book_id` int(11) NOT NULL,`book_name` varchar(255) DEFAULT NULL,`price` int(11) DEFAULT NULL,`stock` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;/*Data for the table `t_book` */insert  into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'仙逆',30,100),(2,'诛仙',20,100);/*Table structure for table `t_user` */DROP TABLE IF EXISTS `t_user`;CREATE TABLE `t_user` (`user_id` int(11) NOT NULL,`username` varchar(100) DEFAULT NULL,`balance` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;/*Data for the table `t_user` */insert  into `t_user`(`user_id`,`username`,`balance`) values (1,'zhangsan',500);

java Bean

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Integer userId;private String username;private Integer balance;
}

1


2、数据库驱动

JDBC(Java DataBase Connectivity),即Java数据库连接。简而言之,就是通过Java语言来操作数据库。

JDBC是sun公司提供一套用于数据库操作的接口.

java程序员只需要面向这套接口编程即可。不同的数据库厂商(MySQLOracleDB2、SQLServer 等),需要用实现类去实现这套接口,再把这些实现类打包(数据驱动jar包),并提供数据驱动jar包给我们使用。   

驱动:就是一个jar包,里面包含了JDBC的实现类

想要通过JDBC连接并操作Mysql数据库,我们需要下载一个Mysql数据库驱动jar包。所以我们下面都能看到引入了这个依赖

<!-- mysql驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>

3、JDBC编程六步

第一步:注册驱动    作用:告诉Java程序,即将要连接的是哪个数据库

通过DriverManager.registerDriver(driver)注册驱动

String className = "com.mysql.cj.jdbc.Driver";
//1. 注册驱动
Class clazz = Class.forName(className);
Driver driver = (Driver) clazz.newInstance();
DriverManager.registerDriver(driver);

第二步:获取连接    表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信使用完之后一定要关闭通道

  • 通过DriverManager.getConnection(url,user,pwd)获取连接
  • Connection连接对象不能随意创建,最后使用完要手动关闭
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/spring-boot-demo?serverTimezone=UTC";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);

第三步:获取数据库操作对象    专门执行sql语句的对象

  • 一个数据库连接对象可以创建多个数据库操作对象
  • 通过conn.createStatement()获取数据库操作对象
//3.获取数据库操作对象
Statement stmt = conn.createStatement();

第四步:执行SQL语句    DQL DML…

  • JDBC编写SQL语句不需要以分号结尾
  • 数据库管理系统会将编写好的SQL语句编译并执行
 //4.执行查询 SQL语句,返回结果集String username = "zhangsan";String sql = "select * from t_user where  username= '" + username + "'";ResultSet rs = stmt.executeQuery(sql);

第五步:处理查询结果集    只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。如果不是的话,可以直接释放资源

根据查询结果集中字段的下标获取

//5.通过索引来遍历读取结果集
while (rs.next()) {int userId = rs.getInt(1);String name = rs.getString(2);String balance = rs.getString(3);System.out.println("userId:" + userId + " 姓名:" + name + " 余额:" + balance);
}


第六步:释放资源    使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭

  • 一个Connection可以创建多个Statement一个Statement可以得出多个ResultSet,所以先关闭ResultSet,再关闭Statement,最后关闭Connection
//5.资源的释放,讲道理要写到finally语句块中
rs.close();
stmt.close();
conn.close();

完整代码

@SpringBootTest
public class TestA {@Testpublic void test2() throws Exception {String className = "com.mysql.cj.jdbc.Driver";//1. 注册驱动Class clazz = Class.forName(className);Driver driver = (Driver) clazz.newInstance();DriverManager.registerDriver(driver);//2. 获取连接String url = "jdbc:mysql://127.0.0.1:3306/spring-boot-demo?serverTimezone=UTC";String user = "root";String password = "root";Connection conn = DriverManager.getConnection(url, user, password);//3.获取数据库操作对象Statement stmt = conn.createStatement();//4.执行查询 SQL语句,返回结果集String username = "zhangsan";String sql = "select * from t_user where  username= '" + username + "'";ResultSet rs = stmt.executeQuery(sql);//5.通过索引来遍历读取结果集while (rs.next()) {int userId = rs.getInt(1);String name = rs.getString(2);String balance = rs.getString(3);System.out.println("userId:" + userId + " 姓名:" + name + " 余额:" + balance);}//5.资源的释放,讲道理要写到finally语句块中rs.close();stmt.close();conn.close();}
}

4、PreparedStatement

SQL注入实例

定义SQL语句框架的时候,使用PreparedStatement数据库操作对象,这个是预编译对象,先将SQL语句框架进行了编译,然后给参数?动态赋值

Statement 和 PreparedStatement 对比
PreparedStatement可以防止 SQL 注入,执行效率高
SQL语句对于Statement来说是:编译一次执行一次
SQL语句对于PreparedStatement来说,是编译一次执行N次
原因:数据库管理系统(DBMS)厂商实现了JDBC接口,DBMS将编译后的SQL语句保存在DBMS中,由于DBMS中有很多编译好的SQL语句,这时通过同一个PreparedStatement对象进行赋值,便会找到其对应的PreparedStatement对象,从而实现其参数的赋值,即:一次编译多次执行。
PreparedStatement是类型安全的,编译期可以检查传入参数类型


5、JDBC事务

  • JDBC默认情况下,事务是自动提交的:即在JDBC中执行一条DML语句就执行了一次事务
  • 将事务的自动提交,修改为手动提交即可避免自动提交
  • 在事务执行的过程中,任何一步出现异常都要进行回滚

开启事务(设置手动提交事务):conn.setAutoCommit(false);

事务提交:conn.commit();

事务回滚:conn.rollback();

Connection con = null;try {con = getConnection();con.setAutoCommit(false);/** do what you want here.*/con.commit();} catch (Throwable e) {if (con != null) {try {con.rollback();// 设定setAutoCommit(false)若没有在catch中进行Connection的rollBack操作,操作的表就会被锁住,造成数据库死锁。虽然在执行con.close()的时候会释放锁,但若应用服务器使用了数据库连接池,连接不会被断开,从而不会放锁} catch (SQLException e1) {e1.printStackTrace();}}throw new RuntimeException(e);} finally {if (con != null) {try {con.close();} catch (SQLException e) {e.printStackTrace();}}}

SpringBoot整合JdbcTemplate

1、启动器依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mysql驱动 -->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>

2、yml配置数据源

数据源DataSource有什么作用?   通过DataSource可以获取数据库连接Connection

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver

3、启动时数据库初始化

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driverinitialization-mode: alwayscontinue-on-error: trueschema:- "classpath:db/schema.sql"data:- "classpath:db/data.sql"

默认加载schema.sql与data.sql。

spring.datasource.schme

schema为表初始化语句,

spring.datasource.data

data为数据初始化,

spring.datasource.initialization-mode  初始化模式(springboot2.0),其中有三个值,always为始终执行初始化,embedded只初始化内存数据库(默认值),如h2等,never为不执行初始化。

spring.datasource.continue-on-error: false   遇到语句错误时是否继续,若已经执行过某些语句,再执行可能会报错,可以忽略,不会影响程序启动


4、JdbcTemplate的使用

定义一个UserDao操作 t_uer 表

@Repository
public class UserDao {@Autowiredprivate JdbcTemplate jdbcTemplate;/*** 根据用户名获取用户** @param uname 用户名* @return 对应的用户*/public User selectByName(String uname) {String sql = "select * from t_user where username = ?";/*** sql是要执行的 SQL 查询。* rowMapper是一个回调,它将每行映射一个对象* args 是要绑定到查询的参数。*/User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), uname);return user;}
}

查一条数据为一个实体类对象

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestA {@AutowiredUserDao userDao;@Testpublic void test1() {User user = userDao.selectByName("zhangsan");System.out.println(user);}
}

结果

JdbcTemplate封装了许多SQL操作,具体可查阅官方文档JdbcTemplate (Spring Framework 6.0.12 API)


SpringBoot整合mybatis

1、依赖

mybatis-spring-boot-starter:

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>

注意:有2种方式可以指定重新指定mysql的版本


2、yml配置

server:port: 8089  #tomcat端口号
logging:level:com.atguigu.dao: debug # 配置日志
spring:datasource:username: rootpassword: 123456url: jdbc:mysql://127.0.0.1:3306/springboot?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driver
mybatis:type-aliases-package: com.atguigu.pojo

指定mybati sql映射文件位置

如数据库里这个字段是这样的user_id,实体类里是这样的userId。mybatis默认不开启驼峰命名规则。即默认情况下无法匹配。

mybatis默认不开启驼峰命名规则。即默认情况下无法匹配。

不用去指定全局配置文件mybatis-config.xml; 而使用mybatis.configuration去代替它。


3、@Mapper注解

mybatis接口要创建代理对象,原来是通过sqlSession.getMapper(UserMapper.class),现在加上@Mapper才行

告诉mybatis这是一个Mapper接口,来操作数据库

@Mapper
public interface AccountMapper{public Account getAcct(Long id);}

当然,每一个mapper接口都加的话太麻烦了,直接在启动类里做处理就好了

 @MapperScan(basePackage=“”),扫描包,提供代理对象

@SpringBootApplication
@MapperScan(basePackages = "com.atguigu.dao")
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class,args);}
}

 在测试类 MybatisTest【一定要被启动类覆盖】

@RunWith(SpringJUnit4ClassRunner.class) //指定Junit核心运行类
@SpringBootTest //自动提供IOC容器
public class MybatisTest {@Autowiredprivate UserMapper userMapper ;@Testpublic void findAll() {List<User> users = userMapper.selectAll();System.out.println(users);}
}

测试结果:因为没有重写toString


4、整合mybatis-plus

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.0</version>
</dependency>

配置文件

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourceinitialization-mode: alwayscontinue-on-error: trueschema:- "classpath:db/schema.sql"data:- "classpath:db/data.sql"hikari:minimum-idle: 5connection-test-query: SELECT 1 FROM DUALmaximum-pool-size: 20auto-commit: trueidle-timeout: 30000pool-name: SpringBootDemoHikariCPmax-lifetime: 60000connection-timeout: 30000
logging:level:com.xkcoding: debugcom.xkcoding.orm.mybatis.plus.mapper: trace
mybatis-plus:mapper-locations: classpath:mappers/*.xml#实体扫描,多个package用逗号或者分号分隔typeAliasesPackage: com.xkcoding.orm.mybatis.plus.entityglobal-config:# 数据库相关配置db-config:#主键类型  AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";id-type: auto#字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"field-strategy: not_empty#驼峰下划线转换table-underline: true#是否开启大写命名,默认不开启#capital-mode: true#逻辑删除配置#logic-delete-value: 1#logic-not-delete-value: 0db-type: mysql#刷新mapper 调试神器refresh: true# 原生配置configuration:map-underscore-to-camel-case: truecache-enabled: true

SpringBoot整合 JPA

1、依赖

spring  data JPA 对  hibernate做了封装,底是Hibernate

添加Spring Data JPA的起步依赖

        <!-- springBoot JPA的起步依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- MySQL连接驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>

2、yml配置

在application.yml中配置数据库和jpa的相关属性

日志级别:
fatal error warn info debug   级别越低,信息越多

logging:level:com.atguigu.dao: debug # 配置日志
spring:datasource:username: rootpassword: 123456url: jdbc:mysql://127.0.0.1:3306/springboot?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverjpa:database: mysql  #数据库类型show-sql: true  #显示sqlgenerate-ddl: true #生成表结构hibernate:ddl-auto: update  #自动更新表结构naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy  #表名字和字段名字的命名策略
server:port: 8089  #tomcat端口号

@Id   标明主键
@GeneratedValue  设置主键自增

id属性上没写@Column

很重要的一个类,会根据这个类在数据库中生成表

@Entity
@Table(name = "user")
public class User{@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(name = "username")private String username;@Column(name = "password")private String password;@Column(name = "name")private String name;//此处省略setter和getter方法... ...
}

UserDao 

public interface UserDao extends JpaRepository<User,Long> {
}

 spring data JPA添加和更新都是save方法、怎么区分:user有没有id

之后编写Controller和service层,这俩层代码跟以前一样,没啥水平。特别强调的是,咱没写mapper映射文件也没写UserDao的实现类,鬼知道怎么就好使了呢

项目跑起来,让自动创建表

idea控制台打印输出 

我们给表里添加俩条数据,然后在postman中发送请求  http://localhost:8089/user/findAll

响应数据如下:

1


SpringBoot事务管理器

1、事务管理器

加入事务管理器,在启动类上加上@EnableTransactionManagement

就可以使用@Transactional事务管理了

TransactionManager是一个空接口

事务管理器 是声明式事务的基础。不管是注解还是xml,这个都得配

在applicationContext.xml中

transaction-manager属性的默认值是transactionManager
如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性


<!--    配置事务管理器--><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"></property></bean><!--    开启事务的注解驱动--><tx:annotation-driven transaction-manager="transactionManager" />

事务:针对连接对象,连接由数据源dataSource提供

原生的:connection连接对象来管理事务


2、开启事务的注解驱动

@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Transactional {}

开启后通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器处理事务

方式一:[废弃,使用springboot]

环绕通知来实现,因为左边这个标志哈

方式二:【推荐】

当然在之后的springboot项目中,就不这么麻烦了,直接在启动类上加

@EnableTransactionManagement


3、模拟场景:买书

Spring声明式事务 本质就是使用了aop 切面编程,对加了@Transactional注解的方法或者service类的对象进行了扩展。方法调用前后进行了事务管理

模拟场景:买书

模拟买书三步骤,需要进行事务管理

  1. 查询图书价格
  2. 更新图书库存
  3. 更新用户余额

没有事务管理的情况

BookServiceImpl中的买书方法,注意此时buyBook()上没有加@Transactional。

   @Override public void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);}

测试如下代码:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class BookControllerTest {@Autowiredprivate BookController bookController;@Testpublic void test1(){bookController.buyBook(1,1);}
}

报错是情理之中,

因为:从下面2表数据(测试方法执行前)可看得出,admin这个用户余额只有80,它买一本价格为100的《斗破苍穹》就会报错,因为表结构中余额是无符号整数。

那么由于我们没有事务管理,此时表中的数据就不对劲了

t_user中数据不会变化,因为执行这条sql的时候报错了嘛,但是t_book这个表里面的库存减一了,也就是“买书三步骤”中的第三步报错了,但是第二步依旧没回滚。


加入事务管理解决

以 @Transactional 注解为植入点的切点,这样才能知道@Transactional注解标注的方法需要被代理。

如何避免上面那种错呢,很简单就是加一个注解@Transactional,搞定!

    @Override@Transactionalpublic void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);}

 我们把t_book中的库存,改回为100,再执行一遍测试代码

bookController.buyBook(1,1);

报错肯定是要报错的,

 但是这时候,t_book表中的库存并没有改,说明事务回滚了!nice

在实际开发中好多都是业务逻辑出错导致的回滚:如库存不够和余额不够。

就拿卖书的例子来说,要是我们没有把金额设置为无符号整数,对数据库来说,整数也可以为负,从逻辑来说,金额不能为负,所以解决方案有2种:

1、从数据库方面来解决:无符号整数unsigned
2、从Java代码方面来解决:没有异常给造一个异常

4、@Transactional 事务属性

@Transactional就是用来定位连接点,可以标识在类或方法上。

@Transactional注解:通过该注解所标识的方法或类中所有的方法会被事务管理器处理事务 

​​​​​​​@Transactional原理是aop 对调用的目标方法进行了扩展
1.方法调用前开启事务 
2.方法调用时它对方法进行了try...catch,如果进入到catch中则回滚事务
3.如果try的最后一行代码能执行到则提交事务

事务的属性

事务的属性有 只读、超时时间、回滚策略、隔离级别、传播行为

    @Transactional(readOnly = false,timeout = 30,//noRollbackForClassName = "java.lang.ArithmeticException",noRollbackFor = ArithmeticException.class,isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRES_NEW)

但是不要慌,事务的这些属性都有默认值

1、事务的只读

通过@Transactional注解的readOnly属性设置,默认值为false

若当前的事务设置为只读,则会在数据库层面去优化该操作,比如不加锁!

注意:只有事务中只有查询功能时,才可以设置事务的只读,即readOnly = true
若设置为只读的事务中有任何的增删改操作,则抛出异常:
java.sql.SQLException: Connection is read-only. 

2、事务的超时时间

通过@Transactional注解的timeout属性设置,默认值为-1,表示往死里等(单位秒)

因为事务针对连接来操作,如果连接一直处于阻塞,就会无限制等待下去

注意:若当前事务设置了事务的超时时间,但是在指定时间内没有执行完毕则抛出事务超时异常TransactionTimedOutException,并将事务强制回滚

我们可以让线程阻塞! TimeUnit.SECONDS.sleep(5);

    @Override@Transactional(timeout = 3)public void buyBook(Integer bookId, Integer userId) {try {//线程暂停5秒,可读性强TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}//查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);}

运行结果,不出所料


3、事务的回滚策略

声明式事务 默认只针对运行时异常回滚,编译时异常不回滚。

  • 运行时异常:不受检异常,没有强制要求try-catch,都会回滚。例如:ArrayOutOfIndex,OutofMemory,NullPointException

  • 编译时异常:受检异常,必须处理,要么try-catch要么throws,都不回滚。例如:FileNotFoundException

通过@Transactional注解的rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName设置

rollbackFor和rollbackForClassName指定的异常必须回滚 ,一般不设置
noRollbackFor和noRollbackForClassName指定的异常不用回滚


rollbackFor和noRollbackFor通过异常的class对象设置
rollbackForClassName和noRollbackForClassName通过异常的全类名设置

noRollbackFor举例

用80去买价格为50一本的《斗罗大陆》,是可以正常买成功的;

但是我们程序最后加了一个数学运算异常,那么按道理来说,会回滚事务。

但是加了noRollbackFor之后,再发生数学运算异常的时候,事务就不会回滚了。。

    @Override@Transactional(noRollbackFor = ArithmeticException.class)public void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price = bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);System.out.println(1/0);}

结果就是,发生了数学运算异常,但是事务没有回滚,数据库数据依旧发生了改变


4、事务的隔离级别

通过@Transactional注解的isolation设置事务的隔离级别,一般使用数据库默认的隔离级别.

隔离级别越高,数据一致性就越好,但并发性越弱。

isolation = Isolation.DEFAULT;//表示使用数据库默认的隔离级别
isolation = Isolation.READ_UNCOMMITTED;//表示读未提交
isolation = Isolation.READ_COMMITTED;//表示读已提交
isolation = Isolation.REPEATABLE_READ;//表示可重复读
isolation = Isolation.SERIALIZABLE;//表示串行化

5、事务的传播行为

事务的传播:当A事务方法调用了B事务方法,A方法在执行时,就会将其本身的事务传播给B方法
B方法执行的过程中,使用A传播过来的事务,也可以使用其本身即B的事务

更详细的可以参考: 
22-05-13 西安 jdbc(03) 事务的ACID属性、并发问题、隔离级别;事务传播行为、本地事务_£小羽毛的博客-CSDN博客_jdbc事务 并发

场景模拟:结账

创建CheckoutServiceImpl,这里我们有个很重要的事情,就是结账:checkout()

它是一个事务方法,它调用了BookServiceImpl的事务方法buyBook()

场景模拟:结账
@Service
public class CheckoutServiceImpl implements CheckoutService {@Autowiredprivate BookService bookService;@Override@Transactionalpublic void checkout(int[] bookIds, int userId) {for (int bookId : bookIds) {bookService.buyBook(bookId, userId);}}
}

在默认情况下,即不改变事务的默认属性:

测试:修改用户的余额为120,目的为了让用户可以买第一本书,但是在买第二本的时候,会因为余额不够而报出异常。

bookController.checkout(new int[]{1,2},1);

此时,观察数据库结果发现俩张表数据都无变化,当“有一本书不能买,那就一本书都买不了”

默认的事务传播属性就是,propagation = Propagation.REQUIRED

表示使用A方法传播到B中的事务,若B方法执行的过程中,只要有抛出异常,整个A方法都要回滚,这是默认

---------------------------------------

可以通过propagation属性设置事务的传播行为,现在,不用默认属性了。改为

propagation = Propagation.REQUIRES_NEW

表示在B方法执行中不使用A方法传播到B中的事务,而是开启一个新事务,即使用B本身的事务。
若B方法执行的过程中,只要有抛出异常,B事务方法回滚,A不会回滚,即不影响A中的其他事务的执行(如第二次买书失败不影响第一次买书成成功)

修改代码,重新测试

继续用上面的测试代码测试,提醒一下,现在用户余额是120,他买的起第一本书,买不起第二本

bookController.checkout(new int[]{1,2},1);

测试后,数据库数据发生了改变,也就是从以前的"有一本书不能买,那就一本书都买不了",变成了现在的“能买几本买几本”。


5、事务失效

  • service没有托管给spring
 spring事务生效的前提是,service必须是一个bean对象
  • 方法不是public的
@Transactional只能用于public的方法上,否则会失效
  • 调用本类方法
调用该类自己的方法,而没有经过spring的代理类,默认只有在外部调用事务才会生效
  • 异常被吃
 将异常try catch 没有throw出来。回滚异常没有,无法回滚
  • 异常类型错误
spring默认只会回滚非检查异常和error异常
如果是抛出受检异常,需要在注解@Transactional加属性rollbackFor
  • final修饰方法
因为spring事务是用动态代理实现,因此如果方法使用了final修饰,则代理类无法对目标方法进行重写,植入事务功能

====================

spring的事务是声明式事务,而声明式事务的本质是SpringAOP,SpringAOP的本质是动态代理。事务要生效必须代理对象在调用。

自调用问题

通过this调用同一个service中的方法,this是指service实现类对象本身,不是代理对象,就相当于方法中的代码粘到了大方法里面,相当于还是一个方法。(会导致事务失效)

@Override
@Transactional
public void bigSave(SpuVo spuVo) {/// 1.保存spu相关// 1.1. 保存spu基本信息 spu_infoLong spuId=saveSpu();// 1.2. 保存spu的描述信息 spu_info_descthis.saveSpuDesc(spuVo, spuId);//1.3、保存spu的基本属性this.saveSpuBaseAttrs(spuVo, spuId);// 2. 保存sku相关信息this.saveSkus(spuVo, spuId);//给mq中发送消息this.sendMessage(spuId);
}

自调用问题-解决办法一

通过其他service对象(spuDescService)调用,这个service对象本质是动态代理对象

自调用问题-解决办法二

this.方法名()替换成this代理对象.方法名()即可。

在类中获取代理对象分三个步骤:

  1. 导入aop的场景依赖:spring-boot-starter-aop

  2. 开启AspectJ的自动代理,同时要暴露代理对象:@EnableAspectJAutoProxy(exposeProxy=true)

  3. 获取代理对象:SpuInfoService proxy = (SpuInfoService) AopContext.currentProxy();


 数据库连接池

1、spring.datasource

  • spring.datasource.continue-on-error在初始化数据库时,遇到错误是否继续,默认false

2、数据库连接池

JDBC的数据库连接池使用 javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现;

数据库连接池(connection pool)概念:本质上是个集合容器,里面存放着数据库的连接。

  • 系统初始化时,创建一定数量的连接对象放入连接池。
  • 当有需要时,从连接池中获取空闲的连接对象,对数据库进行操作
  • 使用完毕后,将该连接对象归还至连接池,方便后续复用

数据库连接池的设计思想:消除频繁创建对象和释放资源带来的延迟,提高系统性能


3、Hikari

Spring Boot 2.0 以上默认使用 Hikari 数据源

spring-boot-starter-jdbc默认的数据库连接池是HikariCP

HikariCP是开源的一个数据库连接池组件,代码非常轻量,并且速度非常的快。

HiKariCP 号称业界跑得最快的数据库连接池,更是被 Spring Boot 2.0 选中作为其默认数据库连接池


4、第三方数据源

如不想使用Springboot默认支持的4种数据源,还可以选择使用其他第三方的数据源,例如:Druid、c3p0等。以使用Druid数据源为例。

<!-- druid数据源驱动 -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.6</version>
</dependency>

定义数据源
使用注解@Bean 创建一个DataSource Bean并将其纳入到Spring容器中进行管理即可。

spring:datasource:druid:# 数据库访问配置, 使用druid数据源type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: oracle.jdbc.driver.OracleDriverurl: jdbc:oracle:thin:@localhost:1521:ORCLusername: testpassword: 123456# 连接池配置initial-size: 5min-idle: 5max-active: 20# 连接等待超时时间max-wait: 30000# 配置检测可以关闭的空闲连接间隔时间time-between-eviction-runs-millis: 60000# 配置连接在池中的最小生存时间min-evictable-idle-time-millis: 300000validation-query: select '1' from dualtest-while-idle: truetest-on-borrow: falsetest-on-return: false# 打开PSCache,并且指定每个连接上PSCache的大小pool-prepared-statements: truemax-open-prepared-statements: 20max-pool-prepared-statement-per-connection-size: 20# 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙filters: stat,wall# Spring监控AOP切入点,如x.y.z.service.*,配置多个英文逗号分隔aop-patterns: com.springboot.servie.*

切换默认数据源,不需要吗???

通过在核心配置中通过spring.datasource.type属性指定数据源的类型


5、Druid

Druid(德鲁伊)是Alibaba提供的数据库连接池,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。

Druid 可以很好的监控 DB 池连接和 SQL 的执行情况,天生就是针对监控而生的 DB 连接池。

添加上 Druid 数据源依赖

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>    <groupId>com.alibaba</groupId>   <artifactId>druid</artifactId>    <version>1.1.21</version>
</dependency>

切换数据源; Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。

spring:datasource:type: com.alibaba.druid.pool.DruidDataSource

设置Druid数据源连接初始化大小、最大连接数、等待时间、最小连接数 等设置项;

spring:datasource:username: rootpassword: 123456#?serverTimezone=UTC解决时区的报错url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8driver-class-name: com.mysql.cj.jdbc.Drivertype: com.alibaba.druid.pool.DruidDataSource#Spring Boot 默认是不注入这些属性值的,需要自己绑定#druid 数据源专有配置initialSize: 5minIdle: 5maxActive: 20maxWait: 60000timeBetweenEvictionRunsMillis: 60000minEvictableIdleTimeMillis: 300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: truetestOnBorrow: falsetestOnReturn: falsepoolPreparedStatements: true#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入#如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4jfilters: stat,wall,log4jmaxPoolPreparedStatementPerConnectionSize: 20useGlobalDataSourceStat: trueconnectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

6、Druid数据源监控

Druid是阿里巴巴开发的号称为监控而生的数据库连接池!

  • 充当数据库连接池。

  • 可以监控数据库访问性能

  • 获得SQL执行日志

相关文章:

SpringBoot整合数据库连接

JDBC 1、SQL准备 DROP TABLE IF EXISTS t_book;CREATE TABLE t_book (book_id int(11) NOT NULL,book_name varchar(255) DEFAULT NULL,price int(11) DEFAULT NULL,stock int(11) DEFAULT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;/*Data for the table t_book */insert…...

uni-app:canvas-绘制图形4(获取画布宽高,根据画布宽高进行图形绘制)

效果 代码 var width ; var height ; const query uni.createSelectorQuery(); //获取宽度 query.select(#firstCanvas).fields({ size: true }, (res) > { width res.width; height res.height; }).exec(); console.log(宽度width); console.log(高…...

EM@坐标@函数@图象的对称和翻折变换

文章目录 abstract翻折变换关于坐标轴翻折 f ( − x ) , f ( x ) f(-x),f(x) f(−x),f(x) − f ( x ) , f ( x ) -f(x),f(x) −f(x),f(x) 偶函数奇函数小结 其他翻折变换关于 y x y\pm x yx对称的直角坐标 关于 x u 对称 关于xu对称 关于xu对称的函数关于 y v yv yv对称的两…...

Python之json模块

JSON (JavaScript Object Notation)&#xff0c;由 RFC 7159 (它取代了 RFC 4627) 和 ECMA-404 指定&#xff0c;是一个受 JavaScript 的对象字面值句法启发的轻量级数据交换格式。JSON独立于编程语言的文本格式来存储和表示数据&#xff0c;现在大部分的数据传输基本使用的都是…...

机器学习---BP算法

1. 多级网络 层号确定层的高低&#xff1a;层号较小者&#xff0c;层次较低&#xff0c;层号较大者&#xff0c;层次较高。 输入层&#xff1a;被记作第0层。该层负责接收来自网络外部的信息。 第j层&#xff1a;第j-1层的直接后继层&#xff08;j>0&#xff09;&#xff…...

继苹果、联发科后,传高通下一代5G芯片将由台积电以3纳米代工

台积电3纳米又有重量级客户加入。市场传出&#xff0c;继苹果、联发科之后&#xff0c;手机芯片大厂高通下一代5G旗舰芯片也将交由台积电以3纳米生产&#xff0c;最快将于10月下旬发表&#xff0c;成为台积电3纳米第三家客户。 针对相关传闻&#xff0c;至昨日&#xff08;25日…...

【自定义类型】--- 位段、枚举、联合

&#x1f493;博客主页&#xff1a;江池俊的博客⏩收录专栏&#xff1a;C语言进阶之路&#x1f449;专栏推荐&#xff1a;✅C语言初阶之路 ✅数据结构探索&#x1f4bb;代码仓库&#xff1a;江池俊的代码仓库&#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐ 文…...

区块链(9):java区块链项目的Web服务实现之实现web服务

1 引入pom依赖 <dependency><groupId>org.eclipse.jetty</groupId><artifactId>jetty-server</artifactId><version>9.4.8.v20171121</version></dependency><dependency><groupId>org.eclipse.jetty</groupId…...

【CV】各种库安装报错及解决办法

目录 1.Error&#xff1a;Cannot unpack file… 1.Error&#xff1a;Cannot unpack file… 使用命令pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn 包名安装 参考&#xff1a;解决Python使用pip安装库文件出现“Error&a…...

【算法系列篇】哈希表

文章目录 前言1. 两数之和1.1 题目要求1.2 做题思路1.3 Java代码实现 2. 判断是否为字符重排2.1 题目要求2.2 做题思路2.3 Java代码实现 3. 存在重复元素3.1 题目要求3.2 做题思路3.3 Java代码实现 4. 存在重复元素II4.2 题目要求4.2 做题思路4.3 Java代码实现 5. 字母异位词分…...

计算机视觉——飞桨深度学习实战-起始篇

后面我会直接跳到实战项目&#xff0c;将计算机视觉的主要任务和目标都实现一遍&#xff0c;但是需要大家下去自己多理解和学习一下。例如&#xff0c;什么是深度学习&#xff0c;什么是计算机视觉&#xff0c;什么是自然语言处理&#xff0c;计算机视觉的主要任务有哪些&#…...

vscode中运行脚手架项目报表

必选在cmd页面里面安装脚手架离谱啊,不然无法执行npm命令啊 vscode运行vue项目_小何不秃头06的博客-CSDN博客 finereport激活成功 - 帆软 (fanruan.com)...

中睿天下荣获2023全国智能驾驶测试赛车联网安全比赛第一名

9月24日&#xff0c;由工业和信息化部、公安部、交通运输部、中国科学技术协会、北京市人民政府共同主办的2023世界智能网联汽车大会展览会在北京闭幕。同期举行的全国智能驾驶测试赛&#xff08;京津冀赛区&#xff09;宣布比赛结果&#xff0c;中睿天下凭借过硬的产品实力&am…...

opencv图像数组坐标系

在OpenCV的Python接口&#xff08;cv2&#xff09;中&#xff0c;加载的图像数组遵循以下坐标系和方向约定&#xff1a; 1. **坐标系&#xff1a;** OpenCV的坐标系遵循数学中的坐标系&#xff0c;原点&#xff08;0, 0&#xff09;位于图像的左上角。横轴&#xff08;X轴&…...

zookeeper mac安装

目录 1.下载zookeeper安装包 2.解压安装包 3.修改配置文件 4.启动服务端 5.启动客户端 这边工作中用到了zookeeper组件&#xff0c;但自己独立安装弄的不太多&#xff0c;这边本机mac装一个做测试使用 以下是安装记录&#xff0c;可以作为参考 从以下链接zookeeper版本列…...

js生成随机16进制数

在JavaScript中&#xff0c;可以使用以下的代码来生成一个100位的随机十六进制数&#xff1a; function generateRandomHex(length) {var result ;var characters 0123456789abcdef;for (var i 0; i < length; i) {result characters.charAt(Math.floor(Math.random() …...

第七章 查找 八、B树

目录 一、定义 二、B树的核心特性 1、B树各个结点的子树数和关键字数 2、子树高度 3、关键字的值 4、B树高度 三、B树的插入 四、B树的删除 一、定义 B树&#xff0c;又称多路平衡查找树&#xff0c;B树中所有结点的孩子个数的最大值称为B树的阶&#xff0c;通常用m表示…...

Vue以及整合ElementUI

初始化vue项目 #vue 脚手架使用 webpack 模板初始化一个 appname 项目 vue init webpack appname启动 vue 项目 #项目的 package.json 中有 scripts&#xff0c;代表我们能运行的命令 npm start npm run dev #启动项目 npm run build&#xff1a;将项目打包项目结构 运行流程…...

免费、丰富、便捷的资源论坛——Yiove论坛,包括但不限于阿里云盘、夸克云盘、迅雷云盘等等

引言 目前资源的数量达到了60000&#xff0c;六万多的资源意味着在这里几乎可以找到任何你想要的资源。 当然&#xff0c;资源并不是论坛的全部&#xff0c;其中还包括了技术交流、福利分享、最新资讯等等。 传送门&#xff1a;YiOVE论坛 - 一个有资源有交流&#xff0c;有一…...

1.3 互联网的组成

思维导图&#xff1a; 前言&#xff1a; 我的笔记&#xff1a; #### 一、总览 - **互联网的结构**&#xff1a; - 具有全球覆盖和复杂的拓扑结构。 - 即便结构复杂&#xff0c;还是可以从工作方式上简化为两大部分&#xff1a;边缘部分和核心部分。 #### 二、边缘部分 -…...

【机器学习】熵和概率分布,图像生成中的量化评估IS与FID

详解机器学习中的熵、条件熵、相对熵、交叉熵 图像生成中常用的量化评估指标通常有Inception Score (IS)和Frchet Inception Distance (FID) Inception Score (IS) 与 Frchet Inception Distance (FID) GAN的量化评估方法——IS和FID&#xff0c;及其pytorch代码...

Vue3.0跨端Web SDK访问微信小程序云储存,文件上传路径不存在/文件受损无法显示问题(已解决)

整理需求&#xff1a; 需要vue3.0作为pc端的后台管理来连接微信小程序客户端需要Web SDK的引入&#xff0c;实现vue3.0接入云开发环境需要以云环境作为线上服务器&#xff0c;将vue3.0上传的本地文件通过云环境进入云储存&#xff0c;并将文件在云端生成云端快捷访问路径及htt…...

使用chat GPT 生成一个js 生成天数的方法

function calculateDaysDifference(targetDateString) {const currentDate new Date();const targetDate new Date(targetDateString);// 计算毫秒差异const timeDifference targetDate - currentDate;// 计算天数差异&#xff0c;如果结果为负数&#xff0c;则设置为0const…...

BUUCTF reverse wp 76 - 80

[CISCN2018]2ex 四处游走寻找关键代码 int __fastcall sub_400430(int a1, unsigned int a2, int a3) {unsigned int v3; // $v0int v4; // $v0int v5; // $v0int v6; // $v0unsigned int i; // [sp8h] [8h]unsigned int v9; // [sp8h] [8h]int v10; // [spCh] [Ch]v10 0;for…...

科技资讯|AirPods Pro基于定位控制的自适应音频功能

在接受 TechCrunch 媒体采访时&#xff0c;苹果高管 Ron Huang 和 Eric Treski 谈到了关于 AirPods Pro 自适应音频&#xff08;Adaptive Audio&#xff09;功能的轶事&#xff0c;曾考虑基于 GPS 信号来控制自适应音频级别。 Treski 表示在探索自适应音频功能初期&#xff0…...

《Jetpack Compose从入门到实战》第九章 Accompanist 与第三方组件库

目录 AccompanistSystemUiControllerPagerSwipeRefreshFlow LayoutInsets LottieCoilAsyncImageSubcomposeAsyncImageAsyncImagePainter Accompanist 最新可用版本accompanist官方文档 SystemUiController 依赖&#xff1a;implementation “com.google.accompanist:accompa…...

Centos7 docker 容器内root身份应用自启动 /usr/sbin/init 问题

Centos7 docker 容器内root身份应用自启动 & /usr/sbin/init 问题 环境&#xff1a;我在一个 docker 容器内手动安装了 mysql、nginx、autotestsystem&#xff08;自己的服务&#xff09;&#xff1b; mysql 和 nginx 都做了服务脚本&#xff1a;mysqld.service、nginx.se…...

STL学习笔记之容器

首先我们要学习的是容器 第一个是容器的初始化&#xff08;构造方式&#xff09;有三种方式 分别是 第一种 int arr[]{1,2,3} vector<int> v1(arr,arr3) 即容器存放的种类和从另外一个数组去拷贝一段数据。 第二种 vector<int> v2(3,10); 第一个3是指存放…...

Java基础---第十二篇

系列文章目录 文章目录 系列文章目录一、获取一个类Class对象的方式有哪些?二、ArrayList 和 LinkedList 的区别有哪些?三、用过 ArrayList 吗?说一下它有什么特点?一、获取一个类Class对象的方式有哪些? 搞清楚类对象和实例对象,但都是对象。 第一种:通过类对象的 get…...

Acwing 841. 字符串哈希

Acwing 841. 字符串哈希 题目描述思路讲解代码展示 题目描述 思路讲解 代码展示 #include <iostream> #include <algorithm>using namespace std;typedef unsigned long long ULL;const int N 100010, P 131; // P 131 或者13331(经验值)int n, m; char str[N]…...

适合美工的设计网站/汕头seo网站建设

C deepin 访问类成员函数(cin.getline())方式是从访问结构成员变量方式衍生而来; C结构体变量申明 struct关键字可省略; c结构体变量声明初始化, 可省略;但此需用在c,大家都知道C 11有很多的新 特性 C结构体可以讲String Object作为其成员; struct Ruiy { //#include <stri…...

巴彦淖尔网站制作开发/郑州seo代理外包公司

1.这里账户查一下。 2.修改配置文件 find / -name beeswax 选择这个带src的。 vim 该文件 DOWNLOAD_CELL_LIMIT Config( keydownload_cell_limit, # 表格限制的大小&#xff0c;行数 * 列数。加一个0即可&#xff0c;修改后可下载的行数变成100万 default10000000, typeint, …...

网站委托建设合同/上海最近3天疫情情况

今天我们一起来了解“Git协作流程”&#xff0c;协作必须有一个规范的流程&#xff0c;让大家有效地合作&#xff0c;使得项目井井有条地发展下去。"协作流程"在英语里&#xff0c;叫做"workflow"或者"flow"&#xff0c;原意是水流&#xff0c;比…...

做服装网站需要什么/网络销售都是诈骗公司吗

Fedora 中的所有软件都必须从其源代码构建。我们不包括预构建的二进制文件。-- Ankur Sinha "franciscod"在上一篇文章中&#xff0c;我们研究了什么是 RPM 软件包。它们是包含文件和元数据的档案文件。当安装或卸载 RPM 时&#xff0c;此元数据告诉 RPM 在哪里创建或…...

福州建设网站设计/网页自动点击软件

翻译自http://dev.mysql.com/doc/refman/5.6/en/index-btree-hash.html 理解B-Tree和Hash的数据结构能够帮助我们预测不同存储引擎下的查询性能差异。存储引擎在索引中使用这些数据结构&#xff0c;尤其是MEMORY 同时提供了B-Tree和Hash索引让你选择。 B-Tree索引特性 B-Tree索…...

微信小程序怎么退出登录/怎么优化关键词排名优化

一个用来恢复被误删除的文件的小程序 写了一个挺有意义的小程序&#xff0c;共享出来&#xff0c;万一你也遇到了&#xff0c;可以拿来用用。 我有两个文件被误删&#xff0c;一个是bcm_ipmc.h&#xff0c;一个是bcm_ipmc.c. 我写了一个小程序&#xff0c;虽然由于恢复得太晚&…...