• 一文学会Spring JDBC 使用


    Spring JDBC

    1、JDBC

    JDBC 就是 数据库开发 操作的 代名词,因为只要是现代商业项目的开发那么一定是离不开 数据库 的,不管你搞的是什么,只要是想使用动态的开发结构,那么一定就是 JDBC ,那么下面首先来回顾一下传统JDBC的使用。

    image-20220914095032178

    JDBC有四种连接: 像JDBC-ODBC的连接已经确定不再使用了、主要采用的是 JDBC网络连接模式

    • 在JDBC的开发之中,一定要 配置相应数据库的驱动程序 后才可以使用,所以这就属于标准的做法,同时还有一点必须明确,不管未来出现了什么样的 Java数据库开发框架,那么核心的本质只有一点: JDBC,可是JDBC 标准里面所定义的 操作结构 是属于 较为底层 的操作形式,所以使用起来 非常的繁琐,因为几乎所有的数据库的项目都需要加载驱动、创建数据库连接、数据库的操作对象、关闭数据库,只有中间的数据库的CRUD操作是有区别的,那么就需要考虑对JDBC进行封装了,那么这个时候就有了ORM组件(全称ORMapping、对象关联映射,采用对象的形式实现JDBC的开发操作)。

    image-20220914095337977

    ​ 从历史的发展上来讲,ORMapping组件出现较多:JDO、Entity Bean、Hibernate、IBatis、SpringJDBC、MyBatis、JPA标准,当然随着技术的发展与淘汰,基本上现在阶段剩下的ORM组件,常用的就是MyBatis(国内互联网公司)、JPA(国外机构),而SpringJDBC是属于JDBC的轻度包装组件(其他的组件都属于重度包装),所以使用SpringJDBC可以 简化JDBC 传统开发里面繁琐的操作步骤。

    image-20220914095705883

    添加依赖

    <properties>
            <maven.compiler.source>17maven.compiler.source>
            <maven.compiler.target>17maven.compiler.target>
            <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
            <spring.version>5.3.21spring.version>
            <mysql.version>8.0.30mysql.version>
    properties>
    
    <dependencies>
        	
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-contextartifactId>
            dependency>
    		
            <dependency>
                <groupId>org.springframeworkgroupId>
                <artifactId>spring-jdbcartifactId>
            dependency>
    			
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
        
        	
            <dependency>
                <groupId>org.junit.jupitergroupId>
                <artifactId>junit-jupiter-apiartifactId>
                <version>5.8.2version>
            dependency>
    
        	
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>1.18.24version>
            dependency>
         	<dependency>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-apiartifactId>
                <version>1.7.25version>
            dependency>
    		
            <dependency>
                <groupId>org.slf4jgroupId>
                <artifactId>slf4j-log4j12artifactId>
                <version>1.7.25version>
            dependency>
        dependencies>
    

    log4j.properties 日志配置文件 (当启动程序,没有任何报错,但是没有信息打印时,需要配置日志)

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
    
    #控制台输出的相关设置
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.ImmediateFlush=true
    log4j.appender.console.Target=System.err
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
    
    
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/logFile.log
    log4j.appender.file.MaxFileSize=10mb
    log4j.appender.file.Threshold=DEBUG
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
    
    #日志输出级别
    log4j.logger.org.mybatis=DEBUG
    log4j.logger.java.sql=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    
    

    2、使用

    要想使用JDBC,配置数据源,是关键性的一步。

    2.1、配置数据源:

    sql脚本

    drop database if exists yootk;
    create database yootk character set utf8;
    use yootk;
    
    create table book
    (
        bid    bigint auto_increment comment '图书id',
        title  varchar(50) not null comment '图书名称',
        author varchar(50) not null comment '图书作者',
        price  double comment '图书价格',
        constraint pk_bid primary key  (bid)
    ) engine = innodb;
    

    2.1.1、注册数据源对像

    创建数据源的配置类:(基于配置类的方式)

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    import javax.sql.DataSource;
    @Configuration
    public class DataSourceConfig {
        @Bean
        public DataSource dataSource() {
            // 驱动数据源
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            // 加载驱动程序
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
            dataSource.setUrl("jdbc:mysql://localhost:3306/yootk"); 
            dataSource.setUsername("root"); 
            dataSource.setPassword("317311");
            return dataSource;
        }
    }
    

    创建数据源的配置类:(基于xml的方式)

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:c="http://www.springframework.org/schema/c"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/yootk"/>
            <property name="username" value="root"/>
            <property name="password" value="317311"/>
        bean>
    beans>
    

    2.1.2、测试:

    import look.word.jdbc.config.DataSourceConfig;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit.jupiter.SpringExtension;
    import javax.sql.DataSource;
    
    @ContextConfiguration(classes = DataSourceConfig.class) //	两者二选一即可
    //@ContextConfiguration(locations ={"classpath:data-source.xml"})  
    @ExtendWith(SpringExtension.class)
    public class TestDataSource {
    	// 日志工厂对象
        private static final Logger LOGGER = LoggerFactory.getLogger(TestDataSource.class);
        @Autowired
        private DataSource dataSource;
        @Test
        public void testConnection() throws Exception{
            LOGGER.info("【数据库连接对象】:{}",dataSource);
        }
    }
    
    // 执行结果 输入数据源对象,说明连接成功
    // [INFO ] 2022-09-14 12:18:59,307(386) --> [main] look.word.test.TestDataSource.testConnection(TestDataSource.java:29): 【数据库连接对象】:org.springframework.jdbc.datasource.DriverManagerDataSource@535779e4  
    

    但是基于这种连接操作的性能是非常一般的,请追随源代码,一探究竟。

    image-20220914122624085

    然后找到我们的AbstractDriverBasedDataSource.getConnection()方法,进入getConnectionFromDriver()方法。

    image-20220914122819627

    找到getConnectionFromDriver(),他是一个抽象方法,然后找到其子类,DriverManagerDataSource

    image-20220914123010388

    然后又会发现,我们回到了DriverManagerDataSource,然后我们在进入getConnectionFromDriverManager方法。

    image-20220914123314843

    最终获取连接的方式,

    image-20220914123430763

    2.1.3、默认连接方式的缺点

    ​ 这种连接的管理方式,是在每一次 获取连接 的时候 才进行 数据库连接的操作了,那么现在的问题就来了,这样的管理方式好吗 ?首先在数据库连接的处理之中,一定会建立若干个Socket 连接,那么会有耗时,而在数据库关闭的时候也会存在有同样的耗时处理,这样在“次次次高并发”的处理下很难得到有效的控制。所以在实际项目中最佳数据库连接的管理,一定是基于数据库连接池方式实现的。所以此时可以考虑在 Spring 内部去实现一个连接池的维护。早期的数据库连接池组件提供有一个 C3P0组件,但是现在已经停止维护了。

    2.2、HikariCP

    ​ 在实际的项目应用开发过程之中,为了解决JDBC连接与关闭的延时以及性能问题,提供了数据库连接池的解决方案,并且针对于该方案提供了成型的HikariCP服务组件。HikariCP (Hikari来自日文,是“光”的含义)是由日本程序员开源的一个数据库连接池组件,该组件拥有如下特点:

    • 宇节码更加的精简,这样可以在缓存中添加更多的程序代码;
    • 实现了一个无锁集合,减少了并发访问造成的资源竞争问题;
    • 使用了自定义数组类型(FastList)代替了ArrayList,提高了get()与remove()的操作性能;
    • 针对CPU的时间片算法进行了优化,尽可能在一个时间片内完成所有处理操作。

    ​ 在Spring之中默认推荐的数据库连接池组件就是HikariCP,不建议再使用其他的数据库连接池组件,当然国内也有优秀的CP组件,那么就是阿里推出的Druid(在性能上可能低于HikariCP,但是提供有完整的管理界面),如果要想使用这个组件,可以采用如下的步骤进行配置。

    2.2.1、使用

    添加依赖:

    			<dependency>
                    <groupId>com.zaxxergroupId>
                    <artifactId>HikariCPartifactId>
                    <version>5.0.1version>
                dependency>
    

    编写配置类:

    这次我们再用配置文件的方式,方便扩展

    • 创建配置文件:src/main/profiles/dev/config/database.properties

    image-20220914205239179

    yootk.database.driverClassName=com.mysql.cj.jdbc.Driver
    yootk.database.jdbcUrl=jdbc:mysql://localhost:3306/yootk
    yootk.database.username=root
    yootk.database.password=317311
    # 【Hikaricp】配置数据库连接超时时间 单位【毫秒】
    yootk.database.connectionTimeOut=3000
    # 【Hikaricp】一个连接最小维持的时间 单位【毫秒】
    yootk.database.idleTimeOut=3000
    # 【Hikaricp】一个连接最长存活的时间 单位【毫秒】
    yootk.database.maxLifetime=6000
    # 【Hikaricp】最大保存的数据库连接实例
    yootk.database.maximumPoolSize=60
    # 【Hikaricp】最小保存的数据库连接实例 (在没有任何用户访问时,最少维持的连接数量)
    yootk.database.minimumIdle=20
    # 【Hikaricp】是否为只读
    yootk.database.readOnly=false
    

    创建配置对象

    @Configuration
    //读取指定位置的资源文件
    @PropertySource("classpath:config/database.properties") 
    public class HikariCpDataSourceConfig {
        /**
         * 绑定资源文件中的配置数据项
         */
        @Value("${yootk.database.driverClassName}")
        private String driverClassName;
        @Value("${yootk.database.jdbcUrl}")
        private String jdbcUrl;
        @Value("${yootk.database.username}")
        private String username;
        @Value("${yootk.database.password}")
        private String password;
        @Value("${yootk.database.connectionTimeOut}")
        private Long connectionTimeOut;
        @Value("${yootk.database.idleTimeOut}")
        private Long idleTimeOut;
        @Value("${yootk.database.maxLifetime}")
        private Long maxLifetime;
        @Value("${yootk.database.maximumPoolSize}")
        private Integer maximumPoolSize;
        @Value("${yootk.database.minimumIdle}")
        private Integer minimumIdle;
        @Value("${yootk.database.readOnly}")
        private boolean readOnly;
    
        @Bean("dataSource")
        public DataSource dataSource() {
            // Hikari连接池数据源
            HikariDataSource dataSource = new HikariDataSource();
            dataSource.setDriverClassName(driverClassName);
            dataSource.setJdbcUrl(jdbcUrl);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            dataSource.setPassword(password);
            // 超时时间
            dataSource.setConnectionTimeout(connectionTimeOut);
            // 空闲超时
            dataSource.setIdleTimeout(idleTimeOut);
            // 连接的最长时间
            dataSource.setMaxLifetime(maxLifetime);
            // 连接池最大数量
            dataSource.setMaximumPoolSize(maximumPoolSize);
            // 当没有连接时 最小保留的连接数量
            dataSource.setMinimumIdle(minimumIdle);
            // 是否只读数据库
            dataSource.setReadOnly(readOnly);
            return dataSource;
        }
    }
    

    测试类:

    import look.word.jdbc.config.HikariCpDataSourceConfig;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit.jupiter.SpringExtension;
    
    import javax.sql.DataSource;
    @ContextConfiguration(classes = HikariCpDataSourceConfig.class)
    @ExtendWith(SpringExtension.class)
    public class TestDataSource {
        private static final Logger LOGGER = LoggerFactory.getLogger(TestDataSource.class);
    
        @Autowired
        private DataSource dataSource;
    
        @Test
        public void testConnection() throws Exception {
            LOGGER.info("【数据库连接对象】:{}", dataSource.getConnection());
        }
    }
    

    ​ 如果出错,可以看看日志输入信息。

    这样我们就实现了,使用HikariCP获取连接对象了,接下来就会使用HikariCP对具体的数据库进行操作。

    2.3、JdbcTempLate

    JdbcTempLate的使用很简单,只需要为其指定数据源即可。

    我们采用配置类的方式,为其配置数据源

    2.3.1、增

    添加配置类:

    @Configuration
    public class JdbcTempLateConfig {
        @Bean // 方法形参 会自动从容器中注入对象
        public JdbcTemplate jdbcTemplate(DataSource dataSource) {
            JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
            return jdbcTemplate;
        }
    }
    

    编写测试类:

    @ContextConfiguration(classes = {HikariCpDataSourceConfig.class, JdbcTempLateConfig.class})
    @ExtendWith(SpringExtension.class)
    public class TestJdbcTempLate {
        private static final Logger LOGGER = LoggerFactory.getLogger(TestJdbcTempLate.class);
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Test
        public void testConnection() throws Exception {
            String sql = "insert into book(title,author,price) values('java入门','李老师',99.90)";
            LOGGER.info("【插入执行结果】:{}", jdbcTemplate.update(sql));
        }
    }
    
    

    执行结果:

    image-20220914211813766

    这个时候就是用JdbcTemplate轻松地实现了数据的插入操作。

    但是,可以发现,我们上面的操作,还是存在问题的,比如没有对sql 进行预处理,会出现 Sql 注入的风险。

    2.3.2、改

    测试类

       @Test
        public void testUpdate() {
            String sql = "update yootk.book set title = ? where bid = ?";
            LOGGER.info("【插入执行结果】:{}", jdbcTemplate.update(sql, "Python入门", 2));
        }
    

    2.3.3、删

    测试类

        @Test
        public void testDelete() {
            String sql = "delete from yootk.book  where bid = ?";
            LOGGER.info("【插入执行结果】:{}", jdbcTemplate.update(sql, 2));
        }
    

    2.3.4、增 (返回id)

    ​ 在MySQL数据库里面,有一种功能,可以通过一个next()处理函数获取当前所生成的ID号(主要针对于自动增长列),实际上这个功能主要的目的是为了解决增加数据时的ID返回处理问题了,因为很多的时候需要在数据增加成功之后对指定的ID进行控制,所以才提供了专属的处理函数,Oracle之中直接使用序列即可,但是MySQL的实现就需要专属的处理函数了。.在程序的开发之中,如果要想获取到增长后的ID数据,在SpringJDBC里面提供有了一个KeyHolder接口,在这个接口里面定义了获取主键内容的处理方法。

    ​ 在平常开发中,我们经常会遇到,插入这个数据后,会需要这个数据的id,然后对其进行一系类操作。

    ​ 如果要想获取到增长后的ID数据,在SpringJDBC里面提供有了一个KeyHolder接口,在这个接口里面定义了获取主键内容的处理方法。

    测试类

        @Test
        public void testInsertReturnId() {
            String sql = "insert into yootk.book(title,author,price) values(?,?,?)";
            GeneratedKeyHolder keyHolder = new GeneratedKeyHolder(); // 获取KEY的处理信息
            int count = jdbcTemplate.update(new PreparedStatementCreator() {
                @Override
                public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                    PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); // 对sql进行预处理
                    ps.setString(1, "Springboot实战");
                    ps.setString(2, "老李");
                    ps.setDouble(3, 99.00);
                    return ps;
                }
            }, keyHolder);
            LOGGER.info("【插入执行影响行数】:{},当前插入数据的ID:{}", count, keyHolder.getKey());
        }
    // 执行结果
    // look.word.test.TestJdbcTempLate.testInsertReturnId(TestJdbcTempLate.java:61): 【插入执行影响行数】:1,当前插入数据的ID:4  
    

    如果在 PreparedStatement ps = con.prepareStatement(sql);中,没有指定需要返回KEY,则会出现异常。

    2.3.5、批处理

    image-20220914224842420

    测试类:

    这种方式是基于集合的。

    	@Test
        public void testInsertBatch() {
            List titles = List.of("Springboot开发实战", "SSM开发案例", "Netty开发实战", "Redis开发实战");
            List prices = List.of(90.1, 98.9, 78.9, 98.9);
            String sql = "insert into yootk.book(title,author,price) values(?,?,?)";
            this.jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {  // 执行批量插入
                 //@param i  集合索引
                @Override
                public void setValues(PreparedStatement ps, int i) throws SQLException {
                    ps.setString(1, titles.get(i));
                    ps.setString(2, "老李老师");
                    ps.setDouble(3, prices.get(i));
                }
                @Override
                public int getBatchSize() {
                    return titles.size(); //总长度
                }
            });
        }
    

    基于对象

    @Test
        public void testInsertBatch2() {
            List params = List.of(
                    new Object[]{"Spring开发实战", "11", 89.0},
                    new Object[]{"Spring开发实战1", "11", 89.0},
                    new Object[]{"Spring开发实战2", "11", 89.0},
                    new Object[]{"Spring开发实战3", "11", 89.0}
            );
            String sql = "insert into yootk.book(title,author,price) values(?,?,?)";
            int[] result = jdbcTemplate.batchUpdate(sql, params);//批量插入
            System.out.println("result = " + result);
        }
    

    2.3.4、查

    ​ 在数据库操作过程中,除了数据更新操作之外,最为繁琐的就是数据库的查询功能了。由于JdbcTemplate设计的定位属于ORMapping组件,所以就需要在查询完成之后,可以自动的将查询结果转为VO类型的实例,而为了解决该问题,在SpringJDBC中提供了一个RowMapper接口,这个接口可以实现ResultSet向指定对象实例的转换。该接口提供有一个mapRow()处理方法,可以接收查询结果每行数据的结果集,用户可以将指定列取出,并保存在自标VO实例之中

    image-20220914230734654

    查询单个

    Book 对象 根据数据库创建

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Book {
        private Integer bid;
        private String title;
        private String author;
        private Double price;
    }
    

    测试类:

        // 查询单个
        @Test
        public void testQuery() {
            String sql = "select  bid, title, author, price from yootk.book  where bid = ?";
            Book book = jdbcTemplate.queryForObject(sql, new RowMapper() {
                @Override
                public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Book book = new Book();
                    book.setBid(rs.getInt(1));
                    book.setTitle(rs.getString(2));
                    book.setAuthor(rs.getString(3));
                    book.setPrice(rs.getDouble(4));
                    return book;
                }
            }, 3); // 这里的3 是对预处理数据的回填 多个需按照顺序编写
            System.out.println("【queryForObject 查询结果】book = " + book);
        }
    
    查询多个
        // 查询所有
        @Test
        public void testQueryAll() {
            String sql = "select  bid, title, author, price from yootk.book ";
            List list = jdbcTemplate.query(sql, new RowMapper() {
                @Override
                public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Book book = new Book();
                    book.setBid(rs.getInt(1));
                    book.setTitle(rs.getString(2));
                    book.setAuthor(rs.getString(3));
                    book.setPrice(rs.getDouble(4));
                    return book;
                }
            });
            list.stream().forEach(System.out::println);
        }
    
    分页查询
        // 分页
        @Test
        public void testQuerySpAll() {
            int current = 2; // 页数
            int size = 5;// 每页数量
            String sql = "select  bid, title, author, price from yootk.book limit ? ,?  ";
            List list = jdbcTemplate.query(sql, new RowMapper() {
                @Override
                public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
                    Book book = new Book();
                    book.setBid(rs.getInt(1));
                    book.setTitle(rs.getString(2));
                    book.setAuthor(rs.getString(3));
                    book.setPrice(rs.getDouble(4));
                    return book;
                }
            }, (current - 1) * size, size);
            list.stream().forEach(System.out::println);
        }
    
    统计行数
        // 查询行数
        @Test
        public void testQueryCount() {
            String sql = "select  count(*) from yootk.book where title like ?";
            long count = jdbcTemplate.queryForObject(sql, new RowMapper() {
                @Override
                public Long mapRow(ResultSet rs, int rowNum) throws SQLException {
                    return rs.getLong(1);
                }
            }, "%Spring%");
            LOGGER.info("【数据库记录总行数】{}", count);
        }
    

    2.4、事务的控制

    ​ 不管你现在使用的是那一种ORM开发框架,只要你的核心是JDBC,那么所有的事务处理都是围绕着JDBC开展的,而JDBC之中的事务控制是由Connection接口提供的方法:

    • 1、关闭自动事务提交:connection.setAutoCommit(false);
    • 2、事务手工提交: connection.commit();
    • 3、事务回滚: connection.rollback();

    在程序的开发之中事务的使用是存在有前提的:如果某一个业务现在需要同时执行若干条数据更新处理操作,这个时候才会使用到事务控制,除此之外是不需要强制性处理的。

    image-20220915124426879

    按照传统的事务控制处理方法来讲一般都是在业务层进行处理的,而在之前分析过了如何基于AOP 设计思想采用动态代理设计模式实现的事务处理模型,这种操作可以在不侵入业务代码的情况下进行事务的控制,但是代码的实现过程实在是繁琐,现在既然都有了AOP处理模型了,所以对于事务的控制就必须有一个完整的加强。

    ACID事务原则:

    ACID主要指的是事务的四种特点:原子性(Atomicity)、一致性(Consistency)、隔离性或独立性(lsolation)、持久性(Durabilily)四个特征:

    • 原子性(Atomicity):整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样;
    • 一致性(Consistency):一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少;
    • 隔离性(lsolation):隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统;
    • 持久性(Durability):在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚

    Spring事务是对已有JDBC事务的进一步的包装型处理,所以底层依然是JDBC事务控制,而后在这之上进行了更加合理的二次开发与设计,首先先来看一下Spring 与JDBC事务之间的结构图。

  • 相关阅读:
    参考线平滑-CostThetaSmoother-Ipopt
    网络安全:Web 安全 面试题.(CSRF)
    高项 10 项目沟通和干系人管理
    Java中多态
    Git常用命令总结(通俗易懂)
    Mybatis 动态 SQL
    springboot下添加日志模块和设置日志文件输出
    K8S:配置资源管理 Secret和configMap
    【YOLO系列】YOLO.v3算法原理详解
    全栈知识体系
  • 原文地址:https://www.cnblogs.com/look-word/p/16696136.html