代码:
Student类:
/** * @author GYX * @date 2019/12/13 1:05 */ @Entity @Data public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; private String name; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @JoinColumn(name = "card_id") private Card card; @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
Card类:
@Entity @Data public class Card { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; private String cardNo; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY , mappedBy = "card") private Student student; @Override public String toString() { return "Card{" + "id=" + id + ", cardNo='" + cardNo + '\'' + '}'; } }
StudentRepository:
public interface StudentRepository extends JpaRepository<Student, Integer> { }
CardRepository:
public interface CardRepository extends JpaRepository<Card, Integer> { }
StudentTest:
@SpringBootTest @RunWith(SpringJUnit4ClassRunner.class) public class StudentTest { @Autowired private StudentRepository studentRepository; @Autowired private CardRepository cardRepository; //添加数据 @Test public void add() { for (int i = 0; i < 3; i++) { Student student = new Student(); student.setName("stu" + i); student.setId(i + 1); Card card = new Card(); card.setCardNo("card" + i); student.setCard(card); studentRepository.save(student); } } @Test @Transactional public void select() { Student student = studentRepository.findOne(1); System.out.println("1111"); Card stuCard = student.getCard(); System.out.println("2222"); System.out.println(stuCard.toString()); System.out.println("-------------"); Card card = cardRepository.findOne(2); System.out.println("End"); } }
问题:
上述代码中 Student 和Card 使用OneToOne进行关联的且两边都是懒加载方式,但是当我们运行 select()这个方法里面的代码时会发现, Student获取Card时可以懒加载,但Card获取Student时却无法懒加载.
执行:
Student student = studentRepository.findOne(1);
根据上图可以看到,并没有查询student里的card
执行:Card stuCard = student.getCard();
可以看到,从数据库中查询了Card.
但是执行: Card card = cardRepository.findOne(2);
可以看到,即查询了Card,也查询了Student.懒加载并没有起到作用.
分析:
1:
在Card表里由于没有关系字段,因此仅从Card表角度看无法知道拥有该卡的学生是谁(除非在Card表建立Student表的外键student_id,但由于冗余了关系字段,因此很少有人这么干吧【注意sql的join语句是考虑了两张表的】),而从Student角度不一样,由于含有card_id字段可能清楚知道该学生拥有一张卡。
正是由于上面的原因,因此当从Card获取Student时,hibernate为了确定Student表中到底有没有该Card,因此发了一条sql:
select student0_.id as id1_3_0_, student0_.card_id as card_id3_3_0_, student0_.name as name2_3_0_ from student student0_ where student0_.card_id=?
参数既是该Card的id,以此来维护Card与Student的关系。
还有一种解释,但需要理解hibernate的懒加载的机制:代理。
2:
hibernate使用了代理(Proxy),对实体的调用会被代理接受和处理,hibernate可以设置这个代理被调用到的时候去加载数据,从而实现延迟加载。那么对于一个映射对象,要么它有值,要么它是null,对于null值建立代理是没多大作用的,而且也不能对null建立动态代理。那就是说hibernate在对延迟加载建立代理的 时候 要考虑这个映射的对象是否是null。如果是null不需要建立代理,直接把映射的值设置成null,如果映射的对象不为null,那么hibernate就建立代理对象。
简而言之,为null就不能懒加载,为代理对象才能懒加载。
解决方法:
1、将Card类中的OneToOne改为OneToMany(一对多)。
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card") private Set<Student> students;
优点:数据库不用修改
缺点:需要修改Student与Card在代码中的对应关系
2、改为主键关联。
import javax.persistence.*; /** * @author GYX * @date 2019/12/13 1:05 */ @Entity @Data public class Student { @Id @GenericGenerator(name = "PK_Card", strategy = "foreign", parameters ={@org.hibernate.annotations.Parameter(name = "property", value = "card")}) @GeneratedValue(generator = "PK_Card") @Column(name = "id", unique = true, nullable = false) private Integer id; private String name; @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false) @PrimaryKeyJoinColumn private Card card; @Override public String toString() { return "Student{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
@Entity @Data public class Card { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; private String cardNo; @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY , mappedBy = "card",optional = false) private Student student; // @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "card") // private Set<Student> students; @Override public String toString() { return "Card{" + "id=" + id + ", cardNo='" + cardNo + '\'' + '}'; } }
除了改变student表的主键、外键结构外,Student类和Card类也要做相应修改,尤其注意“optional”,要设置为false,否则无法实现懒加载。
优点:不改变Student与Card在代码中的对应关系(一对一)
缺点:改动较大,且使用主键关联具有局限性。
PS:主键关联的局限性
使用主键关联会影响数据存储结构,主键关联是一种强耦合,以上述为例:Card存在时,Student才能存在,Card消亡时,Student也随之消失。这是因为Student的主键依赖于Card主键,Student无法独立存在(就是说必须先有学生卡,才能有学生)。
3、在card表增加一个student表的外键字段STUDENT_ID,并在Card类的@OneToOne下增加@JoinColumn(name = "STUDENT_ID"),去掉mappedBy = "card",即
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "STUDENT_ID") public Student getStudent() { return student; }
优点:不改变Student与Card在代码中的对应关系(一对一)
缺点:需要同时维护Student和Card的两个外键。
4、Hibernate 给我们提供了一种字节码增强技术,帮我们解决这种问题,通过编译器改变 class 解决.这种方法我没有亲自试过,结果未知.
可以考虑使用1,2 中方法解决,3,4中不推荐.
参考文章:
还没有评论,来说两句吧...