五、数据库框架整合

目录

1.了解数据源

2.数据源实现中getConnetion的过程

3.Spring与mybatis结合

4.HikariCp连接池

5.事务

6.Spring的事务管理


建议按专栏顺序学习

1.了解数据源

我们要想使用JDBC来获取一个连接,就要使用驱动管理DriverManager来getConnection来获取连接,在Mybatis中,我们直接使用SqlSession开获取会话对象来操作数据库。

在JDBC中有DataSource接口,SqlSession就是实现了这个接口,然后我们通过DataSource来获取连接。

2.数据源实现中getConnetion的过程

以下都是本人浅显的理解,如有错误,感谢纠正。

(unpooled)未池化的数据源连接就是每次申请连接都要通过DriverManager重新连接,使用完后关闭连接,非常耗时。但是池化技术就可以创建多个连接在一个池中,需要使用时去申请连接,使用完后再将连接返回到连接池中,循环使用。

(pooled)池化数据源连接申请过程 :池化数据源中有两个列表,分别是空闲list和活跃list

1.判断空闲列表中是否还有未使用的连接 2.如果有使用的连接,就直接分配,如果没有就判断连接数是否未达到最大值 3.如果已达到最大值就阻塞此请求,如果还达到最大值就创建一个新的连接并且分配 4.如果以上条件都不满足,就查看活跃list中是否有异常的连接,拿到正在使用的连接中连接时间最长的判断是否超时. 5.如果超时就回滚事务,并且使用此连接 如果并没有超时的,就只能阻塞了。 6.如果阻塞时间过长,就放弃申请连接。

如果在上述过程中申请成功连接,1.验证有效性,如果无效直接丢弃,如果有效在使用。

关闭连接过程:不是真的关闭连接,而是将这个已经使用完的连接尝试加入到空闲列表中。 1.从活跃列表中删除此连接,并且判断该连接的有效性 2.如果无效就抛弃此连接 如果有效就看闲置列表的容量是否已满 3.如果闲置列表已满,我们就抛弃此连接 如果未满再加入空闲列表

3.Spring与mybatis结合

Spring与Mybatis结合,IOC管理SqlSessionFactory替代工具类。

导入依赖

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
  	<!-- 注意,对于Spring 6.0来说,版本需要在3.5以上 -->
    <version>3.5.13</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.31</version>
</dependency>
<!-- Mybatis针对于Spring专门编写的支持框架 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>3.0.2</version>
</dependency

Spring提供了SqlSessionTemplate来获取对象,这样就省去了写工具类的过程,我们可以直接用过SqlSessionTemplate来获取Mapper

使用配置文件来注册SqlSessionStemplate的Bean

@Configuration
@ComponentScan("org.example.entity")
public class MainConfiguration {
    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws IOException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

然后在可以直接通过Template来获取Mapper,以下只是演示过程,Mapper以及对应实体类需要自己去创建

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class);
        SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class);
        sqlSessionTemplate.getMapper(StudentMapper.class);
    }
}

更加方便的我们可以在配置类上再加入一个注解来更简单的获取Mapper

@Configuration
@MapperScan("org.example.mapper")
public class MainConfiguration {
    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws IOException {
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

这样被扫描的Mapper接口就可以直接通过getBean获取,怎么样,是不是更简单了?

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class);
        StudentMapper studentMapper = context.getBean(StudentMapper.class);
    }
}

甚至我们可以省去Mybatis-config的配置文件,全注解使用mybatis,只需要在配置文件中写明数据源就可以

@MapperScan("org.example.mapper")
@Configuration
public class MainConfiguration {
    @Bean
    public DataSource dataSource(){
        //分别配置驱动,数据库,用户名,密码
        return new PooledDataSource("com.mysql.jdbc.Driver","jdbc:mysql://localhost:3306/library_management","root","123456");
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}

再次执行

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class);
        StudentMapper studentMapper = context.getBean(StudentMapper.class);
    }
}

4.HikariCp连接池

我们在数据源源的实现中说到了Spring自己的池化,现在我们来学习一下官方推荐的数据源HikariCp连接池。HikariCp连接池的介绍大家可以去搜索一下,简而言之它的最大优势就是非常快。

首先导入HikariCp连接池的依赖

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>5.0.1</version>
</dependency>

我们将数据源中的实现改为HikariCp数据源的实现

@MapperScan("org.example.mapper")
@Configuration
public class MainConfiguration {
    @Bean
    public DataSource dataSource() {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/library_management");
        hikariDataSource.setUsername("root");
        hikariDataSource.setPassword("123456");
        return hikariDataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }
}

由于HikariCp连接池会打印一些日志信息,我们导入依赖,让日志信息正常显示

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.25</version>
</dependency>

在Main类中加入@slf4j注解就会正常打印日志信息而不是报错

执行一下代码即可

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfiguration.class);
        StudentMapper studentMapper = context.getBean(StudentMapper.class);
    }
}

5.事务

我们都知道数据库中有事务,事务遵循ACID原则,有原子性、一致性、隔离性、持久性。以下是我自己的理解,不保证万确的准确性和正确性,大家理解就好。

原子性:是指单个事务的所有操作,要么全部进行完,要不全部不进行,不会只执行一半停下,而是一次性全部完成,如果执行一部分后发生了一些错误,就进行回滚操作,让所有数据回到事务开始前的状态。

一致性:当事务完成后,所有操作都应该全部正常进行且数据的正确性不能够被破坏,同时所有业务保持一致,比如转账,在转账这个操作进行完成后,转帐方和收款方的金额总数应该保持不变。

隔离性:可能会有许多事务同时处理相同的数据,因此每个事务在数据修改时应该独立进行,防止数据损坏。

持久性:事务完成后,结果应该正常保存。无论发生什么样的系统错误,数据都应该保持事务完成后的状态。

事务的隔离机制:

事务存在四种隔离级别:读未提交,读已提交,可重复读,串行化

读未提交:事务A在进行的时候事务B就可以正常的读取数据,但有时会发生一些错误,比如,当事务A数据进行了修改,而此时事务B读取了该数据,而切好事务A发生一些问题进行了回滚,这样此时事务B读取的数据就是错误的,这种数据叫做“脏数据”,这种现象叫做脏读。

读已提交:事务A在进行的时候事务B不能进行读取事务A需要使用的数据,只能在事务A完成后才能进行读取,此时是不是感觉不会发生错误?其实还是会发生一些问题,注意我们只说了不能读取,没说不能修改数据,比如,当事务A正在进行它读取了一个数据,之后B对此数据进行了修改,事务A在之后又读取了一次该数据,这就造成了在事务B对数据修改的前后事务A读取的数据不一致,这就现象叫做幻读,也叫做不可重复读。

可重复读:事务A在进行的时候事务B依旧不可以进行读取数据,同时还限制了事务B不能修改任何事务A需要使用的数据,怎么样?是不是感觉应该不会有任何问题了呢?但其实,我们限制了读和改,但是没有限制写啊,对的,事务B还是可以正常的进行数据的插入,这样还是会造成一些问题。比如,事务A在进行时读取了某个数据项的数目,之后事务B插入了一条这个数据项,然后事务A有进行了该数据项的数,在事务B插入数据前后,事务A读取数目前后不一致,这种现象叫做幻读。

串行化:串行化直接限制了在某个事务执行时,其他事务不能进行,这样其他事务再也不能造成任何问题,但这个限制却造成了效率的低下。

Mybatis对于事务管理也封装了一个接口Transaction,这个接口中有提交、回滚、关闭、设置超时时间。

此接口的实现有三种方式,分别是jdbc的事务管理机制、MANAGED的事务管理机制、自定义实现transaction接口

1.jdbc事务管理机制:JdbcTransaction

2.MANAGED事务管理机制:Spring实现SpringManagedTransaction

3.自定义实现org.apache.ibatis.transaction.Transaction接口

我们重点掌握Spring事务管理

6.Spring的事务管理

声明式事务基于AOP实现

导入依赖

<!-- Spring的JDBC支持框架 -->
<dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-jdbc</artifactId>
     <version>6.0.10</version>
</dependency>

在配置上设置事务管理的注解@EnableTransactionManagement就开启了Spring 的事务支持,创建事务管理器的Bean

@MapperScan("org.example.mapper")
@Configuration
@ComponentScan("org.example")
@EnableTransactionManagement
public class MainConfiguration {
    @Bean
    public DataSource dataSource() {
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/library_management");
        hikariDataSource.setUsername("root");
        hikariDataSource.setPassword("123456");
        return hikariDataSource;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean;
    }

    @Bean
    public TransactionManager transactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

当给一个方法加入@Transactional后,这个方法就会被当作事务处理,这个方法被调用后,如果在中间发生错误就会自动回滚

@Transactional有几个关键属性,有: transactionManager:指定事务管理器,propagation:事务传播规则,isolation:事务隔离级别,timeout:事务超时时间readOnly:是否为只读事务,rollbackFor和noRollbackFor:发生指定异常时回滚或是不回滚,默认发生任何异常都回滚。

事务的隔离级别已经在上一节详细说过了。事务的传播规则propagation也有七种级别:(较难理解,且以下均为个人理解,若有错,感谢纠正)REQUIRED,SUPPORTS,MANDATORY,NEW,NOT_SUPPORTED,NEVER,NESTED

默认为REQUIRED,当前方法必须运行在事务中。若方法运行在一个事务中,就不会创建新的事务,相当于将这个方法的操作结合进当前事务中,如果发生数据库操作失败,就会将当前方法以及当前事务中的全部操作回滚;若当前方法没有运行在一个事务环境下,就会创建一个一个新的事务供方法运行,也就是将此方法当作一个事务来运行,如果发生数据库操作失败,将当前方法的所有操作回滚。

SUPPORTS,当前方法不必须运行在事务中。若方法运行在一个事务中,就不会创建新的事务,相当于将这个方法的操作结合进当前事务中,如果发生数据库操作失败,就会将当前方法以及当前事务中的全部操作回滚;如果方法没有运行在事务环境下,就当作一个普通方法执行,相当于事务也可以当作普通的数据库操作去使用,这样更加灵活。

MANDATORY,当前方法必须在一个已存在的事务中运行,否则抛出异常。若方法运行在一个事务中,就不会创建新的事务,相当于将这个方法的操作结合进当前事务中,如果发生数据库操作失败,就会将当前方法以及当前事务中的全部操作回滚;如果当前方法没有运行在一个事务中,它不会和required一样去创建一个新的事务供这个方法运行,而是直接抛出异常。

NEW,当前方法必须运行在自己的事务中。当前方法如果运行在一个事务中,就会先将这个旧事务挂起,先创建新的事务运行当前方法,若新的事务数据库操作失败,那么就会回滚新的事务与旧事务,如果新事务运行完,旧事务会被激活然后继续运行,如果旧事务又发生了数据库操作失败,那么只会回滚旧事务,新事务不会再进行回滚;若当前方法没有运行在一个事务环境下,就会创建一个一个新的事务供方法运行,也就是将此方法当作一个事务来运行,如果发生数据库操作失败,将当前方法的所有操作回滚。。

NOT_SUPPORTED:当前方法不支持事务。如果当前方法运行在一个事务中,会将这个事务挂起,然后将此方法独立的作为非事务的方法运行,如果发生异常,当前方法不会回滚,但是被挂起的事务会回滚,若没有发生异常,运行完后再将被挂起的事务激活继续运行;如果当前方法没有运行在一个事务中,就正常独立的作为非事务的方法运行。

NEVER:当前方法不允许运行在事务环境下,必须作为单独的事务进行。如果运行在一个事务中,会抛出异常;如果没有运行在一个事务中,就正常创建一个事务运行此方法。

NESTED:若当前方法如果运行在事务环境下,就作为嵌套事务独立的运行和回滚,也就是如果嵌套事务发生了数据库操作失败,只会回滚自己的操作不会回滚旧事务,如果旧事务发生数据库操作失败,那么旧事物与嵌套事务全部都会回滚;若当前方法没有运行在一个事务环境下,就会创建一个一个新的事务供方法运行,也就是将此方法当作一个事务来运行,如果发生数据库操作失败,将当前方法的所有操作回滚。

最难的地方已经讲解完了,具体的使用大家可以移步Mybatis整合:Spring事务_哔哩哔哩_bilibili 学习。

学习参考:

青空の霞光的个人空间-青空の霞光个人主页-哔哩哔哩视频

青空の霞光-CSDN博客

Spring 事务七大传播机制 —— REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED_mandatory require never-CSDN博客

一个视频教会你spring的事务传播行为_哔哩哔哩_bilibili

如有错误,感谢纠正

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aayasu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值