懒加载和分库分表


文章目录

  • 懒加载和分库分表
  • 1. 概述:
  • 2. 啥是懒加载?
  • 3. 啥是分库分表?
  • 4. 懒加载和分库分表的联系?


1. 概述:

很久之前,在项目压测的时候碰到过一个问题,就是当并发时请求数量上来,会抛出超过数据库连接等待时间的异常。最后查出来是因为Spring Boot中的一个默认开启的属性:spring.jpa.open-in-view,开启这个属性呢,会为了保证懒加载的数据在出了事务后还能被使用,不是进入service层事务方法才打开session,而在服务的view层就直接将session打开,并且也不是在service层事务提交后就把session关闭,而是在整个请求返回的时候,才将session关闭。这样就会导致连接池里面的数据库连接释放不及时,当连接池里面的连接全部被占满的时候,再有请求想要连数据库,就得等待,等待超过默认的6秒后,就会抛异常。最后解决办法是将spring.jpa.open-in-view置为false。由于使用的是springdataJPA,我们的项目好像没有用到懒加载,所以我自己又去看了一下hibernate的懒加载是什么东西,又从这个懒加载反推为啥我们项目没有用到懒加载,最后找到了是因为分库分表的原因,这也是这篇文章的由来。

2. 啥是懒加载?

懒加载字面意思就是:懒得加载,能不加载就不加载。懒加载的好处就是提高速度、减少资源消耗,比如说我的数据库里有两张表:User和Order,我们可以很容易就想到,用户可以下多个订单,那么我们将用户的id作为一个字段存在订单表里面,并且建立主外键关联,就像下面的表格一样:

User表:

id

name

1

小明

Order表:

id

name

user_id

1

10个飞机

1

2

10个火箭

1

我们可以看到,现在用户1:小明,有2个订单——10个飞机、10个火箭,由于User表和Order表有联系,所以我们将User表的id和Order表的user_id作为主外键连起来。我们项目中的实体类如下:

User.java:

public class User{
    private Integer id;
    private String name;
    // get() & set()
}

Order.java:

public class Order{
    private Integer id;
    private String name;
    private User user;
    // get() & set()
}

这边可以在映射文件里面去将两张表和实体类关联起来,或者使用注解,Order -> User :many to one。

接下来我们复现一下懒加载,我们可以看到Order类里面是有一个User对象的,每一个订单对应一个用户,我们首先查询一下order,并且我们看一下在不使用查询得到的order对象的user对象时,user对象的class是什么。

懒加载示例代码:

@Test
public void testLazyLoad(){
    // 拿到session对象
    Order order = (Order) session.get(Order.class, 1);
    System.out.println(order.getName());	// 结果为10个飞机
    User user = order.getUser();
    System.out.println(user.getClass().getName());// 结果为代理对象的类名...User_$$_...
    // 提交事务,关闭session,释放资源
}

执行后,我们可以发现在不使用user对象时,使用的是一个代理对象,反之,如果我们要打印user对象的名字,那么就会发送sql语句去数据库查出order1对应的user对象。这就是懒加载的作用,当我们关联查询时,首先查到的是order1,hibernate会自动使用代理对象去帮我们模拟一个user对象出来,当我们需要使用user对象的属性的时候,才会帮我们去数据库把user查出来,这样的好处是,如果我们不需要使用user,那么就少向数据库查一次,节省时间和资源

值得注意的是,上面的例子中,user对象使用的时候是需要和绑定order对象在同一个session中哦,如果我们在使用user之前把session关闭了,那么就会抛出懒加载异常。

懒加载异常示例代码:

@Test
public void testLazyLoadFail(){
    // 拿到session对象
    Order order = (Order) session.get(Order.class, 1);
    System.out.println(order.getName());	// 结果为10个飞机
    session.close; 							// 关闭session
    User user = order.getUser();
    System.out.println(user.getName());		// 抛出懒加载异常
    // 提交事务,关闭session,释放资源
}

3. 啥是分库分表?

现在是微服务时代,我们的项目会由很多的服务组成,比如有账户服务、支付服务、订单服务。。。每一个服务都是一个springboot项目,一个服务建立一个数据库、管理自己的表,服务之间通过消息队列或者RPC之类的通讯方式一起合作,所以微服务项目常常会根据不同的服务进行分库分表。那么对于我们上面的例子,我们可以开一个服务用来管理用户,再开一个服务用来管理订单,还可以再开一个网关服务用来做数据处理、控制各服务合作的流程。当我们开了这三个服务后,用户下订单我们就可以这么做了:

  1. 下订单的请求先进网关服务,在网关服务转发到用户服务;
  2. 在用户服务去查询用户数据库的用户表,确定用户存不存在等等,判断完后将返回体返还给网关服务;
  3. 网关服务拿到用户服务的返回体后,如果用户存在,就发送请求给订单服务,订单服务往订单数据库的订单表里输入数据,如果不存在就返回相应错误码。

这么一操作后,我们就不需要在订单的实体表order.java里面再去关联用户的表了,也不用将两张表建立主外键的联系。我们也应该在日常开发中少用主外键,主外键非常不利于数据迁移、数据扩展,这就是实际开发中根据不同服务进行分库分表的情况。

4. 懒加载和分库分表的联系?

那么怎么将懒加载和分库分表联系起来呢?懒加载最大的特点就是,有主外键关联的表是放在同一个数据库中的,我们进行分库分表后,一个服务一个库,需要封装不同表里面的数据的时候可以在网关服务里面封装,就几乎不需要用到懒加载啦。这也是为啥我们项目碰到压测问题后,最后解决办法是直接将spring.jpa.open-in-view关闭原因,因为我们项目就没有用到懒加载的特性,哈哈。