JPA問題記錄

上個月試用了下 JPA,發現了一些莫名其妙的問題,記錄一下。java

版本:spring-boot-starter-data-jpa:2.2.0.RELEASE,mysql

MySQL:8.0.16,InnoDB 引擎,RR 隔離級別。spring

0. 初始數據

實體類:sql

@Data
@Entity
@Table(name = "people")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class People {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "username")
    private String username;
}

@Data
@Entity
@Table(name = "cat")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
public class Cat {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "name")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "owner")
    private People owner;
}
複製代碼

表數據:數據庫

mysql> select * from people;
+----+----------+
| id | username |
+----+----------+
|  1 | zhangsan |
|  2 | laoli    |
+----+----------+
2 rows in set (0.00 sec)

mysql> select * from cat;
+----+-------+-------+
| id | name  | owner |
+----+-------+-------+
|  1 | kitty |     1 |
|  2 | mao   |     2 |
|  3 | mi    |     2 |
+----+-------+-------+
3 rows in set (0.00 sec)
複製代碼

1. 在同一事務中更新數據後讀不到最新的值

測試代碼以下:緩存

public interface PeopleRepo extends JpaRepository<People, Integer>, JpaSpecificationExecutor<People> {
    @Transactional
    @Modifying
    @Query(value = "update people set username = :username where id = :id", nativeQuery = true)
    void updateUsernameById(@Param("id") int id, @Param("username") String username);
}

@Service
public class PeopleAndCatService {
    @Autowired
    private PeopleRepo peopleRepo;

    @Transactional
    public String test1(int id) {
        People p1 = peopleRepo.findById(id).get()			// 1
        System.out.println("p1: " + p1);
        peopleRepo.updateUsernameById(id, "ceeeeb");	// 2
        People p2 = peopleRepo.findById(id).get()			// 3
        System.out.println("p2: " + p2);
        return null;
    }
}
複製代碼

當調用接口 test1(1) 時,返回結果以下:session

p1: People(id=1, username=zhangsan)
p2: People(id=1, username=zhangsan)
複製代碼

可是數據庫中的數據已更改。app

緣由:一個事務(Propagation.REQUIRED 模式)對應一個 EntityManager。 執行步驟 1 的查詢時,EntityManager 【SessionImpl】中已經存在 id=1 的實體類緩存,步驟 3 再次查詢時,直接從緩存中獲取,實際上並無查詢數據庫。less

解決方法(參考)數據庫設計

  • 步驟 2 的手寫 sql 改爲 JpaRepository 提供的 save(S entity) 方法;
  • 或者在執行步驟 2 後,清空下該 session 的緩存,@Modifying(clearAutomatically = true)

2. 懶加載致使的 org.hibernate.LazyInitializationException: could not initialize proxy - no Session 錯誤

測試代碼:

@Service
public class PeopleAndCatService {
    @Autowired
    private CatRepo catRepo;
    
    @Transactional
    public Cat test2(int id) {
        Cat c = catRepo.findById(id).get();	// 1
        return c;
    }
}

@RestController
@RequestMapping("/jpa")
public class PeopleAndCatController {
    @Autowired
    private PeopleAndCatService peopleAndCatService;

    @GetMapping("/test2")
    public Cat test2(@RequestParam("id") int id) {
        Cat cat = peopleAndCatService.test2(id);
        return cat;		// 2
    }
}
複製代碼

錯誤的緣由在於:最終在 controller 裏面返回響應的時候,jackson 序列化 Cat 對象失敗,更確切的說是序列化 Cat 對象裏面的 owner 字段的時候報錯。owner 字段是一個 People 對象,因爲咱們設置了懶加載,因此步驟 1 查詢時,實際的 sql 語句是 select * from cat where id = ?,並無關聯查詢 People 表(可是拿到 owner 的 id 了)。

當執行步驟 2 時,須要序列化 People 對象,調用 people.getUsername() 方法時,會觸發數據庫查詢操做(懶加載!),而此時 session 已關閉,所以報錯。

解決方法:

  • 不用懶加載,更改成 @ManyToOne(fetch = FetchType.EAGER)

  • 在事務未結束時,好比步驟 1 後面,調用 c.getOwner().getUsername() 觸發查詢 People 表操做;

  • 上面的方案最終仍是查詢了 People 表,若是我根本就不關心 owner,不想查 People 表呢;能夠改寫方法以下:

    @Transactional
    public Cat test2(int id) {
        Cat c = catRepo.findById(id).get();
        People people = new People();
        people.setId(c.getOwner().getId());
        c.setOwner(people);
        return c;
    }
    複製代碼
  • 不用外鍵,更改 Cat 類的 owner 字段爲 int 類型,涉及關聯查詢時須要手寫 sql,表之間的關聯控制經過程序實現(涉及到數據庫設計中是否須要外鍵之爭了。。。)

相關文章
相關標籤/搜索