jpa关联映射

来源:互联网 发布:mac的哪款粉底液好用 编辑:程序博客网 时间:2024/06/05 03:46

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", "lucy@qq.com",new Date());        //订单关联用户        order.setUser(user);        entityManager.persist(user);        entityManager.persist(order);    }}

执行方法,控制台能看到两条插入语句。
查看数据库:
这里写图片描述
这里写图片描述
添加了订单记录,关联的用户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, email=lucy@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, email=lucy@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", "hession@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, email=hession@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记录的语句,查看数据库表
这里写图片描述
这里写图片描述
插入了数据且已经关联。

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

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