在实际开发中,绝大部分的关联关系都是单向多对一(或一对多),所以我们需要重点掌握。
多对一指关系数据库中第一个表中的多个行只可以与第二个表中的一个行相关,且第二个表中的一个行可以与第一个表中的多个行相关。
下面将通过教师和学生为例,介绍多对一关系。
通常,多对一的关系,可以在多方的表中添加一个外键列来维护双方的关系,如下图:
(1)教师表对应的 Teacher 实体,代码如下:
@Data @Entity @Table public class Teacher { @Id @GeneratedValue private int id; @Column private String name; }
(2)学生表对应的 Student 实体,代码如下:
@Data @Entity @Table public class Student { @Id @GeneratedValue private int id; @Column private String name; // 这里是关键代码 @ManyToOne @JoinColumn(name = "teacher_id") private Teacher teacher; }
从上面代码可以看到,在表结构方面,我们应该是在 student(学生)表中添加外键列来维护学生的教师信息。在对象设计方面,在 Student 对象中关联 Teacher 对象,来封装当前学生的教师。
(1)ersistence.xml 配置
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="demo_manytoone" transaction-type="RESOURCE_LOCAL"> <!-- JPA提供者 --> <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider> <!-- 声明实体类 --> <class>com.hxstrive.openjpa.annotation.demo_manytoone.Teacher</class> <class>com.hxstrive.openjpa.annotation.demo_manytoone.Student</class> <!-- 配置JPA数据库属性 --> <properties> <property name="openjpa.ConnectionURL" value="jdbc:mysql://localhost:3306/openjpa_learn?useSSL=false& serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"/> <property name="openjpa.ConnectionDriverName" value="com.mysql.jdbc.Driver"/> <property name="openjpa.ConnectionUserName" value="root"/> <property name="openjpa.ConnectionPassword" value="aaaaaa"/> <property name="openjpa.Log" value="SQL=TRACE"/> <!-- 自动生成表 --> <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)"/> <!-- 不使用加载时强化和编译时强化,使用运行时Unenhanced(不能发挥OpenJPA的最大效能,所以也不推荐) --> <property name="openjpa.ClassLoadEnhancement" value="false"/> <property name="openjpa.DynamicEnhancementAgent" value="false"/> <property name="openjpa.RuntimeUnenhancedClasses" value="supported"/> </properties> </persistence-unit> </persistence>
(2)Teacher 教师实体
@Data @Entity @Table public class Teacher { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column private String name; }
(3)Student 学生实体
@Data @Entity @Table public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Column private String name; @ManyToOne @JoinColumn(name = "teacher_id") private Teacher teacher; }
(4)客户端代码
import com.alibaba.fastjson.JSONObject; import javax.persistence.*; public class Demo { /** 持久化单元名称 */ private static final String NAME = "demo_manytoone"; private static void insertData(EntityManager em) { try { em.getTransaction().begin(); // 教师 Teacher teacher = new Teacher(); teacher.setName("教师1"); // 学生 Student student1 = new Student(); student1.setName("学生1"); student1.setTeacher(teacher); Student student2 = new Student(); student2.setName("学生2"); student2.setTeacher(teacher); // 持久化学生和教师信息 em.persist(student1); em.persist(student2); em.persist(teacher); em.getTransaction().commit(); } finally { em.close(); } } private static void selectData(EntityManager em) { try { em.getTransaction().begin(); // 查询学生信息 TypedQuery<Student> query = em.createQuery( "select s from Student s", Student.class); for(Student student : query.getResultList()) { System.out.println("student_id=" + student.getId()); System.out.println("student_name=" + student.getName()); System.out.println("student_teacher=" + JSONObject.toJSONString(student.getTeacher())); } em.getTransaction().commit(); } finally { em.close(); } } public static void main(String[] args) { EntityManagerFactory emf = null; try { emf = Persistence.createEntityManagerFactory(NAME); // 插入数据 insertData(emf.createEntityManager()); // 查询数据 selectData(emf.createEntityManager()); } finally { if(null != emf) { emf.close(); } System.out.println("finished."); } } }
运行输出 SQL 日志如下:
INSERT INTO Teacher (id, name) VALUES (?, ?) [params=?, ?] INSERT INTO Student (id, name, teacher_id) VALUES (?, ?, ?) [params=?, ?, ?] INSERT INTO Student (id, name, teacher_id) VALUES (?, ?, ?) [params=?, ?, ?]
因为是在多方(Student)关联一方(Teacher)的数据,所以在保存 Student 的时候需要考虑关系数据的维护(外键列的信息),即 Teacher 的数据。
如果在 OpenJPA 中,将 Id 生成的策略指定为 GenerationType.AUTO(将使用序列表保存ID),SQL 日志如下:
-- 生成数据ID SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?] UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?] SELECT SEQUENCE_VALUE FROM OPENJPA_SEQUENCE_TABLE WHERE ID = ? FOR UPDATE [params=?] UPDATE OPENJPA_SEQUENCE_TABLE SET SEQUENCE_VALUE = ? WHERE ID = ? AND SEQUENCE_VALUE = ? [params=?, ?, ?] -- 插入学生信息 INSERT INTO Student (id, name, teacher_id) VALUES (?, ?, ?) [params=?, ?, ?] INSERT INTO Student (id, name, teacher_id) VALUES (?, ?, ?) [params=?, ?, ?] -- 插入教师信息 INSERT INTO Teacher (id, name) VALUES (?, ?) [params=?, ?] -- 查询学生信息 SELECT t0.id, t0.name, t1.id, t1.name FROM Student t0 LEFT OUTER JOIN Teacher t1 ON t0.teacher_id = t1.id -- 控制台打印学生信息 student_id=1 student_name=学生1 student_teacher={"id":51,"name":"教师1"} student_id=2 student_name=学生2 student_teacher={"id":51,"name":"教师1"} student_id=101 student_name=学生1 student_teacher={"id":151,"name":"教师1"} student_id=102 student_name=学生2 student_teacher={"id":151,"name":"教师1"}
数据表数据如下图: