欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

jpa关联映射

程序员文章站 2024-03-04 09:42:23
...

jpa的对象关联映射主要通过注解来实现,分为一对多,多对多,一对一,这里只列出我实际项目中用到的,不打算写一些花哨的了。前面定义好了一个User实体类,这里再定义个订单实体类Order,一个用户可以有多个订单,一个订单只属于一个客户。

1、单向多对一
User实体:

@Entity
@Table(name = "t_user")
public class User {

    @Id//必须指定主键
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

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

    private String password;

    private String telephone;

    private String email;

    @Column(name="create_time")
    @Temporal(TemporalType.DATE)
    private Date createTime;


    //映射必须定义个空构造器
    public User() {
    }

    public User(String userName, String password, String telephone, String email, Date createTime) {
        super();
        this.userName = userName;
        this.password = password;
        this.telephone = telephone;
        this.email = email;
        this.createTime = createTime;
    }
    public String getUserInfo(){
        return "username:"+this.userName+",email:"+this.email;
    }
    get、set、toString方法
}

Order实体:

@Entity
@Table(name = "t_order")
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    //付款金额
    private Integer payment;
    //支付方式 0:支付宝 1:微信
    private Integer channel;
    //支付状态
    private Integer status=0;
    //创建时间
    @Column(name="create_time")
    @Temporal(TemporalType.DATE)
    private Date createTime;

    //单向多对1,
    @ManyToOne(targetEntity=User.class,fetch=FetchType.LAZY)
    private User user;

    public Order() {
    }
    public Order(Integer payment, Integer channel, Integer status, Date createTime) {
        this.payment = payment;
        this.channel = channel;
        this.status = status;
        this.createTime = createTime;
    }
    get、set、toString方法
}

这里用户没有关联订单,订单关联了用户,所以订单实体有属性
private User user以及get、set方法。
映射单向多对一,使用注解@ManyToOne,使用@JoinColumn(name=”user_id”)指定外键列名为”user_id”,如果不写该注解的话,默认是属性名_id,上面的示例就没有指定外键列名,那么数据库表t_order的外键字段为user_id,但是这时需要指定实体类为User.class,否则执行持久化操作会抛出异常。
(1)、测试单向多对一持久化操作

public class TestMapper1 {
    private EntityManager entityManager;
    private EntityManagerFactory entityManagerFactory;
    private EntityTransaction transaction;

    @Before
    public void init(){
        entityManagerFactory = Persistence.createEntityManagerFactory("jpa-2");
        entityManager = entityManagerFactory.createEntityManager();
        transaction = entityManager.getTransaction();
        transaction.begin();
    }
    @After
    public void destory(){
        transaction.commit();
        entityManager.close();
        entityManagerFactory.close();
    }
    @Test
    //多对一持久化操作
    public void testManyToOne(){
        Order order = new Order(1000, 0, 0, new Date());
        User user = new User("Lucy", "123456", "13322222226", "aaa@qq.com",new Date());
        //订单关联用户
        order.setUser(user);
        entityManager.persist(user);
        entityManager.persist(order);
    }
}

执行方法,控制台能看到两条插入语句。
查看数据库:
jpa关联映射
jpa关联映射
添加了订单记录,关联的用户id为14。

(2)、测试单向多对一查询操作

@Test
    //多对一查询
    public void testManyToOne2(){
        Order order = entityManager.find(Order.class, 1l);
        System.out.println("创建时间:"+order.getCreateTime());
        System.out.println("-----------------------------");
        System.out.println("用户信息:"+order.getUser());
    }
控制台输出:
Hibernate: 
    select
        order0_.id as id1_0_1_,
        order0_.channel as channel2_0_1_,
        order0_.create_time as create_t3_0_1_,
        order0_.payment as payment4_0_1_,
        order0_.status as status5_0_1_,
        order0_.user_id as user_id6_0_1_,
        user1_.id as id1_1_0_,
        user1_.create_time as create_t2_1_0_,
        user1_.email as email3_1_0_,
        user1_.password as password4_1_0_,
        user1_.telephone as telephon5_1_0_,
        user1_.user_name as user_nam6_1_0_ 
    from
        t_order order0_ 
    left outer join
        t_user user1_ 
            on order0_.user_id=user1_.id 
    where
        order0_.id=?
创建时间:2017-08-28
-----------------------------
用户信息:User [id=14, userName=Lucy, password=123456, telephone=13322222226, aaa@qq.com]

从发生的sql语句能看到,使用一条sql语句使用外连接将order关联的user的信息全部查了出来,也就是使用了立即加载策略。而hibernate的默认加载是懒加载,在jpa中如果要使用懒加载,可以修改@ManyToOne的属性fetch
分为:
FetchType.EAGER:立即加载(默认)
FetchType.LAZY:延迟加载

@ManyToOne(targetEntity=User.class,fetch=FetchType.LAZY)
    private User user;

这时候再执行上面的查询语句

Hibernate: 
    select
        order0_.id as id1_0_0_,
        order0_.channel as channel2_0_0_,
        order0_.create_time as create_t3_0_0_,
        order0_.payment as payment4_0_0_,
        order0_.status as status5_0_0_,
        order0_.user_id as user_id6_0_0_ 
    from
        t_order order0_ 
    where
        order0_.id=?
创建时间:2017-08-28
-----------------------------
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.create_time as create_t2_1_0_,
        user0_.email as email3_1_0_,
        user0_.password as password4_1_0_,
        user0_.telephone as telephon5_1_0_,
        user0_.user_name as user_nam6_1_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
用户信息:User [id=14, userName=Lucy, password=123456, telephone=13322222226, aaa@qq.com]

可以发现发送了两条sql,只有需要用到user的时候才进行查询

(3)、测试单向多对一删除
单向多对一删除如果先删除多的一方再删除一的一方是没问题的,但是先删除一的一方肯定是不能执行的,因为一的一方的主键为多的一方的外键,但是可以通过修改@OneToMany的cascade来修改默认的删除策略(级联删除),在一对多的测试时候演示。

    @Test
    //多对1删除
    public void testManyToOne3(){
        Order order = entityManager.find(Order.class, 1l);
        entityManager.remove(order);
    }

(4)、测试单向多对1修改

@Test
    //多对1修改
    public void testManyToOne4(){
        Order order = entityManager.find(Order.class, 1l);
        order.getUser().setUserName("jackson");
    }

输出:三条sql语句,第一条查询订单信息,由于懒加载第二条查询用户信息,最后执行update操作更新用户名。

2、单向一对多

修改User实体类,关联订单,一个用户拥有多个订单,一对多
添加属性

    @JoinColumn(name="user_id")
    @OneToMany
    private Set<Order> orders = new HashSet<>();
    getset方法

注:外键是唯一的,知道外键列名必须跟Order实体类一致。

(1)、测试单向一对多持久化操作

@Test
    public void testOneToMany(){
        Order order = new Order(1000, 0, 0, new Date());
        Order order2 = new Order(999, 1, 0, new Date());
        User user = new User("Hession", "123456", "15822222226", "aaa@qq.com",new Date());
        //用户关联订单
        user.getOrders().add(order);
        user.getOrders().add(order2);
        //持久化操作
        entityManager.persist(user);
        entityManager.persist(order);
        entityManager.persist(order2);
    }

查看控制台输出语句:
一共五条:前三条是插入用户信息和两条订单记录语句。后两条是修改订单信息,添加两条订单记录的user_id,因为这时候外键由一的一方维护,必须一的一方插入后才能拿到id,然后才能设置外键(user_id)。

三条插入语句(一个用户2个订单)
两条更新语句如下:
Hibernate: 
    update
        t_order 
    set
        user_id=? 
    where
        id=?
Hibernate: 
    update
        t_order 
    set
        user_id=? 
    where
        id=?

(2)、测试单向一对多查询

@Test
    public void testManyToOne0(){
        User user = entityManager.find(User.class, 16l);
        System.out.println(user);
        System.out.println("---------------");
        System.out.println(user.getOrders().size());
    }
控制台输出:
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.create_time as create_t2_1_0_,
        user0_.email as email3_1_0_,
        user0_.password as password4_1_0_,
        user0_.telephone as telephon5_1_0_,
        user0_.user_name as user_nam6_1_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
User [id=16, userName=Hession, password=123456, telephone=15822222226, aaa@qq.com]
---------------
Hibernate: 
    select
        orders0_.user_id as user_id6_1_1_,
        orders0_.id as id1_0_1_,
        orders0_.id as id1_0_0_,
        orders0_.channel as channel2_0_0_,
        orders0_.create_time as create_t3_0_0_,
        orders0_.payment as payment4_0_0_,
        orders0_.status as status5_0_0_,
        orders0_.user_id as user_id6_0_0_ 
    from
        t_order orders0_ 
    where
        orders0_.user_id=?
2

从输出能看到,存在懒加载问题,默认策略跟多对一是相反的,同样可以修改@OneToMany的fetch来修改加载策略。

(3)、测试单向一对多删除

单向一对多删除,可以直接对一的一端删除

@Test
    public void testOneToMany2(){
        User user = entityManager.find(User.class, 16l);
        entityManager.remove(user);
    }
控制台输出:
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.create_time as create_t2_1_0_,
        user0_.email as email3_1_0_,
        user0_.password as password4_1_0_,
        user0_.telephone as telephon5_1_0_,
        user0_.user_name as user_nam6_1_0_ 
    from
        t_user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        t_order 
    set
        user_id=null 
    where
        user_id=?
Hibernate: 
    delete 
    from
        t_user 
    where
        id=?

从输出显而易见,先会将t_order表中关联的记录的user_id设为null,这时就不再关联该用户了,接着删除该用户。
注:如果想将该用户下的订单也删除,那么可以通过casecade属性来设置级联删除

    @JoinColumn(name="user_id")
    @OneToMany(cascade=CascadeType.REMOVE)
    private Set<Order> orders = new HashSet<>();

(4)、测试单向一对多修改

    @Test
    public void testOneToMany3(){
        User user = entityManager.find(User.class, 18l);
        user.getOrders().iterator().next().setPayment(1111);
    }

获取持久化状态的对象之间设置属性即可。

3、双向多对一

双向多对一关系,需要留意的是最好让一方维护外键,而外键是在多的一方,一般由多的一方维护外键即可。操作的话跟一对多,多对一是一样。

可以在一的一方来设置,放弃外键维护。

//@JoinColumn(name="user_id")
@OneToMany(fetch=FetchType.LAZY,cascade={CascadeType.REMOVE},mappedBy="user")
private Set<Order> orders = new HashSet<>();

mappedBy=”user”可以理解为一的一方放弃维护外键,外键由多的一方维护,user表示的是多的一方所持有的一的一方的属性。
一旦用了mappedBy,那么便不能再使用@JoinColumn(name=”user_id”)注解。

4、单向一对一
还是用上面的订单作为一方实体类,在自动售货机上使用扫描支付,那么订单和商品就是一对一的关系,添加商品实体类Goods。
由商品实体类来维护关联关系(没必要两边都维护,@OneToOne注解在一边使用即可,包括前面的一对多多对一,通常注解在一边使用即可)。

@Entity
@Table(name="t_goods")
public class Goods {

    @Id
    @GeneratedValue
    private long id;
    private String name;
    private Integer price;

    @Column(name="create_time")
    @Temporal(TemporalType.DATE)
    private Date createTime;

    //一对一关联
    @OneToOne
    private Order order;

    public Goods(String name, Integer price, Date createTime) {
        this.name = name;
        this.price = price;
        this.createTime = createTime;
    }

    public Goods() {
    }
    get、set方法
}

(1)、测试持久化操作

public void testOneToOne(){
        Order order = new Order(2222, 1, 0, new Date());
        Goods goods = new Goods("健力宝", 250, new Date());
        //商品关联订单
        goods.setOrder(order);
        //持久化操作
        entityManager.persist(order);
        entityManager.persist(goods);
    }

控制台会打印两条插入到两个表t_order,t_goods记录的语句,查看数据库表
jpa关联映射
jpa关联映射
插入了数据且已经关联。

至于多对多也是类似,实际使用中建议注解@ManyToMany使用在一边即可(多对多跟hibernate一样必然有一方要放弃维护外键)。

总结:不管是一对多还是多对一,还是一对一还是多对多,或是单向双向。最后使用的时候一般都可以只用单向,即在一方使用注解,最后需要的数据都是能查询到的而不用像学习的时候整的过于繁琐。