jpa关联映射
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);
}
}
执行方法,控制台能看到两条插入语句。
查看数据库:
添加了订单记录,关联的用户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<>();
get,set方法
注:外键是唯一的,知道外键列名必须跟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记录的语句,查看数据库表
插入了数据且已经关联。
至于多对多也是类似,实际使用中建议注解@ManyToMany使用在一边即可(多对多跟hibernate一样必然有一方要放弃维护外键)。
总结:不管是一对多还是多对一,还是一对一还是多对多,或是单向双向。最后使用的时候一般都可以只用单向,即在一方使用注解,最后需要的数据都是能查询到的而不用像学习的时候整的过于繁琐。