Spring 各类知识板块完整解析 你有没有学到吐血?!!!

3 篇文章 0 订阅

                                              目录
      一,Spring Boot原理

     二,JPA原理
           2.1 事务
           2.2 本地事务
           2.3 分布式事务

      三,mybatis 缓存使用
           3.1 一级缓存
           3.2 二级缓存

Spring Boot原理

Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。其特点如下:

1.创建独立的Spring应用程序
2.嵌入的Tomcat,无需部署WAR文件
3.简化Maven配置
4.自动配置Spring
5.提供生产就绪型功能,如指标,健康检查和外部配置
6.绝对没有代码生成和对XML没有要求配置 [1]

JPA原理

1.事务

事务是计算机应用中不可或缺的组件模型,它保证了用户操作的原子性 ( Atomicity )、一致性 ( Consistency )、隔离性 ( Isolation ) 和持久性 ( Durabilily )。

2.本地事务

紧密依赖于底层资源管理器(例如数据库连接 ),事务处理局限在当前事务资源内。此种事务处理方式不存在对应用服务器的依赖,因而部署灵活却无法支持多数据源的分布式事务。在数据库连接中使用本地事务示例如下:

public void transferAccount() {  
                        Connection conn = null;  
                        Statement stmt = null;  
                        try{ 
                                    conn = getDataSource().getConnection(); 
 // 将自动提交设置为 false,若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交                
                                        conn.setAutoCommit(false); 
                                        stmt = conn.createStatement();  
                                        // 将 A 账户中的金额减少 500
                          stmt.execute("update t_account set amount = amount - 500 where account_id = 'A'"); 
                                    // 将 B 账户中的金额增加 500 
                          stmt.execute("update t_account set amount = amount + 500 where account_id = 'B'"); 
                                        // 提交事务 
                            conn.commit();  
                       // 事务提交:转账的两步操作同时成功  
                         } catch(SQLException sqle){ 
                                                   // 发生异常,回滚在本事务中的操做  
                                                    conn.rollback(); 
                                                  // 事务回滚:转账的两步操作完全撤销 
                                                   stmt.close();  
                                                    conn.close();  
                         } 
            } 

3. 分布式事务

Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。分布式事务(Distributed Transaction)包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器承担着所有事务参与单元的协调与控制。

public void transferAccount() {  
                       UserTransaction userTx = null; 
                       Connection connA = null; Statement stmtA = null; 
                       Connection connB = null; Statement stmtB = null;
 try{
         // 获得 Transaction 管理对象 
         userTx = (UserTransaction)getContext().lookup("java:comp/UserTransaction");
         connA = getDataSourceA().getConnection();// 从数据库 A 中取得数据库连接 
          connB = getDataSourceB().getConnection();// 从数据库 B 中取得数据库连接 
           userTx.begin(); // 启动事务  
                stmtA = connA.createStatement();// 将 A 账户中的金额减少 500 
stmtA.execute("update t_account set amount = amount - 500 where account_id = 'A'"); 
                                    // 将 B 账户中的金额增加 500  
                                    stmtB = connB.createStatement(); 
stmtB.execute("update t_account set amount = amount + 500 where account_id = 'B'");                             
                                    userTx.commit();// 提交事务 
             // 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新) 
  } catch(SQLException sqle){ 
               // 发生异常,回滚在本事务中的操纵 
                  userTx.rollback();// 事务回滚:数据库 A 和数据库 B 中的数据更新被同时撤销 
               } catch(Exception ne){ }
 } 

4.两阶段提交

两阶段提交主要保证了分布式事务的原子性:即所有结点要么全做要么全不做,所谓的两个阶段是指:第一阶段:准备阶段;第二阶段:提交阶段。
在这里插入图片描述
准备阶段

事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。

提交阶段

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

将提交分成两阶段进行的目的很明确,就是尽可能晚地提交事务,让事务在提交前尽可能地完成所有能完成的工作。

mybatis 缓存使用

缓存的重要性是不言而喻的。 使用缓存, 我们可以避免频繁的与数据库进行交互, 尤其是在查询越多、缓存命中率越高的情况下, 使用缓存对性能的提高更明显。

Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存1024条SQL。二级缓存是指可以跨SqlSession的缓存。是mapper级别的缓存,对于mapper级别的缓存不同的sqlsession是可以共享的。

1.一级缓存

同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况先, 只执行一次 SQL 语句(如果缓存没有过期)

也就是只有在参数和 SQL 完全一样的情况下, 才会有这种情况。

1.1 同一个 SqlSession

@Test
public void oneSqlSession() {
    SqlSession sqlSession = null;
    try {
        sqlSession = sqlSessionFactory.openSession();

        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        // 执行第一次查询
        List<Student> students = studentMapper.selectAll();
        for (int i = 0; i < students.size(); i++) {
            System.out.println(students.get(i));
        }
        System.out.println("=============开始同一个 Sqlsession 的第二次查询============");
        // 同一个 sqlSession 进行第二次查询
        List<Student> stus = studentMapper.selectAll();
        Assert.assertEquals(students, stus);
        for (int i = 0; i < stus.size(); i++) {
            System.out.println("stus:" + stus.get(i));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

在以上的代码中, 进行了两次查询, 使用相同的 SqlSession, 结果如下
在这里插入图片描述
在日志和输出中:

第一次查询发送了 SQL 语句, 后返回了结果;

第二次查询没有发送 SQL 语句, 直接从内存中获取了结果。

而且两次结果输入一致, 同时断言两个对象相同也通过。

1. 2不同的 SqlSession

@Test
public void differSqlSession() {
    SqlSession sqlSession = null;
    SqlSession sqlSession2 = null;
    try {
        sqlSession = sqlSessionFactory.openSession();

        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        // 执行第一次查询
        List<Student> students = studentMapper.selectAll();
        for (int i = 0; i < students.size(); i++) {
            System.out.println(students.get(i));
        }
        System.out.println("=============开始不同 Sqlsession 的第二次查询============");
        // 从新创建一个 sqlSession2 进行第二次查询
        sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        List<Student> stus = studentMapper2.selectAll();
        // 不相等
        Assert.assertNotEquals(students, stus);
        for (int i = 0; i < stus.size(); i++) {
            System.out.println("stus:" + stus.get(i));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (sqlSession != null) {
            sqlSession.close();
        }
        if (sqlSession2 != null) {
            sqlSession2.close();
        }
    }
}

在代码中, 分别使用 sqlSession 和 sqlSession2 进行了相同的查询。

其结果如下
在这里插入图片描述
从日志中可以看到两次查询都分别从数据库中取出了数据。 虽然结果相同, 但两个是不同的对象。

1.3 刷新缓存

**

刷新缓存是清空这个 SqlSession 的所有缓存, 不单单是某个键。

@Test
public void sameSqlSessionNoCache() {
    SqlSession sqlSession = null;
    try {
        sqlSession = sqlSessionFactory.openSession();

        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        // 执行第一次查询
        Student student = studentMapper.selectByPrimaryKey(1);
        System.out.println("=============开始同一个 Sqlsession 的第二次查询============");
        // 同一个 sqlSession 进行第二次查询
        Student stu = studentMapper.selectByPrimaryKey(1);
        Assert.assertEquals(student, stu);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

如果是以上, 没什么不同, 结果还是第二个不发 SQL 语句。

在此, 做一些修改, 在 StudentMapper.xml 中, 添加

flushCache=“true”

修改后的配置文件如下:

<select id="selectByPrimaryKey" flushCache="true" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List" />
    from student
    where student_id=#{id, jdbcType=INTEGER}
</select>

结果如下:
在这里插入图片描述
第一次, 第二次都发送了 SQL 语句, 同时, 断言两个对象相同出错。

1.4 总结

在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;

不同的 SqlSession 之间的缓存是相互隔离的;

用一个 SqlSession, 可以通过配置使得在查询前清空缓存;

任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。

2 .二级缓存

二级缓存存在于 SqlSessionFactory 生命周期中。

2.1 配置二级缓存

2.1.1 全局开关

在 mybatis 中, 二级缓存有全局开关和分开关, 全局开关, 在 mybatis-config.xml 中如下配置:

<settings>
  <!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 -->
  <setting name="cacheEnabled" value="true"/>
</settings>

默认是为 true, 即默认开启总开关。

2.1.2 分开关

分开关就是说在 *Mapper.xml 中开启或关闭二级缓存, 默认是不开启的。

2.1.3 entity 实现序列化接口

public class Student implements Serializable {

    private static final long serialVersionUID = -4852658907724408209L;
    
    ...
    
}

2.2 使用二级缓存

@Test
public void secendLevelCacheTest() {

    // 获取 SqlSession 对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //  获取 Mapper 对象
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    // 使用 Mapper 接口的对应方法,查询 id=2 的对象
    Student student = studentMapper.selectByPrimaryKey(2);
    // 更新对象的名称
    student.setName("奶茶");
    // 再次使用相同的 SqlSession 查询id=2 的对象
    Student student1 = studentMapper.selectByPrimaryKey(2);
    Assert.assertEquals("奶茶", student1.getName());
    // 同一个 SqlSession , 此时是一级缓存在作用, 两个对象相同
    Assert.assertEquals(student, student1);

    sqlSession.close();

    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    StudentMapper studentMapper1 = sqlSession1.getMapper(StudentMapper.class);
    Student student2 = studentMapper1.selectByPrimaryKey(2);
    Student student3 = studentMapper1.selectByPrimaryKey(2);
    // 由于我们配置的 readOnly="true", 因此后续同一个 SqlSession 的对象都不一样
    Assert.assertEquals("奶茶", student2.getName());
    Assert.assertNotEquals(student3, student2);

    sqlSession1.close();
}

结果如下:

2018-09-29 23:14:26,889 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Created connection 242282810.
2018-09-29 23:14:26,889 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a]
2018-09-29 23:14:26,897 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - ==>  Preparing: select student_id, name, phone, email, sex, locked, gmt_created, gmt_modified from student where student_id=? 
2018-09-29 23:14:26,999 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - ==> Parameters: 2(Integer)
2018-09-29 23:14:27,085 [main] TRACE [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <==    Columns: student_id, name, phone, email, sex, locked, gmt_created, gmt_modified
2018-09-29 23:14:27,085 [main] TRACE [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <==        Row: 2, 小丽, 13821378271, xiaoli@mybatis.cn, 0, 0, 2018-09-04 18:27:42.0, 2018-09-04 18:27:42.0
2018-09-29 23:14:27,093 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper.selectByPrimaryKey] - <==      Total: 1
2018-09-29 23:14:27,093 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.0
2018-09-29 23:14:27,108 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a]
2018-09-29 23:14:27,116 [main] DEBUG [org.apache.ibatis.transaction.jdbc.JdbcTransaction] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@e70f13a]
2018-09-29 23:14:27,116 [main] DEBUG [org.apache.ibatis.datasource.pooled.PooledDataSource] - Returned connection 242282810 to pool.
2018-09-29 23:14:27,124 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.3333333333333333
2018-09-29 23:14:27,124 [main] DEBUG [com.homejim.mybatis.mapper.StudentMapper] - Cache Hit Ratio [com.homejim.mybatis.mapper.StudentMapper]: 0.5

以上结果, 分几个过程解释:

第一阶段:

1.在第一个 SqlSession 中, 查询出 student 对象, 此时发送了 SQL 语句;
2.student更改了name 属性;
3.SqlSession 再次查询出 student1 对象, 此时不发送 SQL 语句, 日志中打印了 「Cache Hit Ratio」, 代表二级缓存使用了, 但是没有命中。 因为一级缓存先作用了。
4.由于是一级缓存, 因此, 此时两个对象是相同的。
5.调用了 sqlSession.close(), 此时将数据序列化并保持到二级缓存中。

第二阶段:

6.新创建一个 sqlSession.close() 对象;
7.查询出 student2 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 3 个对象,但只有一个命中, 因此 命中率 1/3=0.333333;
8.查询出 student3 对象,直接从二级缓存中拿了数据, 因此没有发送 SQL 语句, 此时查了 4 个对象,但只有一个命中, 因此 命中率 2/4=0.5;
9.由于 readOnly=“true”, 因此 student2 和 student3 都是反序列化得到的, 为不同的实例。

2.3 配置详解
查看 dtd 文件, 可以看到如下约束:

<!ELEMENT cache (property*)>
<!ATTLIST cache
type CDATA #IMPLIED
eviction CDATA #IMPLIED
flushInterval CDATA #IMPLIED
size CDATA #IMPLIED
readOnly CDATA #IMPLIED
blocking CDATA #IMPLIED
>

从中可以看出:

cache 中可以出现任意多个 property子元素;
cache 有一些可选的属性 type, eviction, flushInterval, size, readOnly, blocking.

2.3.1 type
type 用于指定缓存的实现类型, 默认是PERPETUAL, 对应的是 mybatis 本身的缓存实现类 org.apache.ibatis.cache.impl.PerpetualCache。

后续如果我们要实现自己的缓存或者使用第三方的缓存, 都需要更改此处。

2.3.2 eviction
eviction 对应的是回收策略, 默认为 LRU。

1.LRU: 最近最少使用, 移除最长时间不被使用的对象。

2.FIFO: 先进先出, 按对象进入缓存的顺序来移除对象。

3.SOFT: 软引用, 移除基于垃圾回收器状态和软引用规则的对象。

4.WEAK: 弱引用, 移除基于垃圾回收器状态和弱引用规则的对象。

2.3.3 flushInterval

flushInterval 对应刷新间隔, 单位毫秒, 默认值不设置, 即没有刷新间隔, 缓存仅仅在刷新语句时刷新。

如果设定了之后, 到了对应时间会过期, 再次查询需要从数据库中取数据。

2.3.4 size
size 对应为引用的数量,即最多的缓存对象数据, 默认为 1024。

2.3.5 readOnly

readOnly 为只读属性, 默认为 false

false: 可读写, 在创建对象时, 会通过反序列化得到缓存对象的拷贝。 因此在速度上会相对慢一点, 但重在安全。

true: 只读, 只读的缓存会给所有调用者返回缓存对象的相同实例。 因此性能很好, 但如果修改了对象, 有可能会导致程序出问题。

2.3.6 blocking

blocking 为阻塞, 默认值为 false。 当指定为 true 时将采用 BlockingCache 进行封装。

使用 BlockingCache 会在查询缓存时锁住对应的 Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据。
在这里插入图片描述
2.4 注意事项

由于在更新时会刷新缓存, 因此需要注意使用场合:查询频率很高, 更新频率很低时使用, 即经常使用 select, 相对较少使用delete, insert, update。

缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响。但刷新缓存是刷新整个 namespace 的缓存, 也就是你 update 了一个, 则整个缓存都刷新了。

最好在 「只有单表操作」 的表的 namespace 使用缓存, 而且对该表的操作都在这个 namespace 中。 否则可能会出现数据不一致的情况。

看到这里的朋友点个赞呀,有收获的小伙伴,给个评论呀!!!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值