2013-01-02 134 views
11

配置JPA坚持一个实体很多关系

  • EcliplseLink 2.3.2
  • JPA 2.0
  • 的实体是从NetBeans中的数据库架构与数据库实体类创建自动...向导。
  • 控制器类自动从NetBeans中创建与实体类JPA控制器类...向导

短版问题

的在一个经典场景中,两个表之一很多关系。我创建了父实体,然后创建了子实体,并将子女附加到了父代的集合中。当我创建(控制器方法)父实体时,我期望子实体被创建并与父关联。为什么不发生?

龙版

父类

@Entity 
@XmlRootElement 
public class Device implements Serializable { 
    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Basic(optional = false) 
    private Integer id; 
    @Column(unique=true) 
    private String name; 
    @Temporal(TemporalType.TIMESTAMP) 
    private Date updated; 
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "deviceId") 
    private Collection<NetworkInterface> networkInterfaceCollection; 

    public Device() { 
    } 

    public Device(String name) { 
     this.name = name; 
     updated = new Date(); 
    } 

    // setters and getters... 

    @XmlTransient 
    public Collection<NetworkInterface> getNetworkInterfaceCollection() { 
     return networkInterfaceCollection; 
    } 

    public void setNetworkInterfaceCollection(Collection<NetworkInterface> networkInterfaceCollection) { 
     this.networkInterfaceCollection = networkInterfaceCollection; 
    } 

    public void addNetworkInterface(NetworkInterface net) { 
     this.networkInterfaceCollection.add(net); 
    } 

    public void removeNetworkInterface(NetworkInterface net) { 
     this.networkInterfaceCollection.remove(net); 
    } 
    // other methods 
} 

儿童类

@Entity 
@Table(name = "NETWORK_INTERFACE") 
@XmlRootElement 
public class NetworkInterface implements Serializable { 
    private static final long serialVersionUID = 1L; 
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    @Basic(optional = false) 
    private Integer id; 
    private String name; 
    @Temporal(TemporalType.TIMESTAMP) 
    private Date updated; 
    @JoinColumn(name = "DEVICE_ID", referencedColumnName = "ID") 
    @ManyToOne(optional = false) 
    private Device deviceId; 

    public NetworkInterface() { 
    } 

    public NetworkInterface(String name) { 
     this.name = name; 
     this.updated = new Date(); 
    } 

    // setter and getter methods... 

    public Device getDeviceId() { 
     return deviceId; 
    } 

    public void setDeviceId(Device deviceId) { 
     this.deviceId = deviceId; 
    } 
} 

主要类

public class Main { 
    public static void main(String[] args) { 
     EntityManagerFactory emf = Persistence.createEntityManagerFactory("wifi-dbPU"); 
     DeviceJpaController deviceController = new DeviceJpaController(emf); 
     NetworkInterfaceJpaController netController = new NetworkInterfaceJpaController(emf); 

     Device device = new Device("laptop"); 
     NetworkInterface net = new NetworkInterface("eth0"); 

     device.getNetworkInterfaceCollection().add(net); 
     deviceController.create(device); 
    } 
} 

这CL屁股抛出NullPointerException行:device.getNetworkInterfaceCollection().add(net);

系统知道有一个新的实体device,它有一个元素net在它的集合。我期望它在db中编写device,获取设备ID,将它附加到net并写入db。

取而代之的是,我发现,这些步骤我必须做的:

deviceController.create(device); 
net.setDeviceId(device); 
device.getNetworkInterfaceCollection().add(net); 
netController.create(net); 

为什么我要创建的儿童在父类知道这是孩子,它应该创建它为我?

DeviceJpaController的创建方法(抱歉在字段中的长名称,它们是自动生成的)。

public EntityManager getEntityManager() { 
    return emf.createEntityManager(); 
} 

public void create(Device device) { 
    if (device.getNetworkInterfaceCollection() == null) { 
     device.setNetworkInterfaceCollection(new ArrayList<NetworkInterface>()); 
    } 
    EntityManager em = null; 
    try { 
     em = getEntityManager(); 
     em.getTransaction().begin(); 
     Collection<NetworkInterface> attachedNetworkInterfaceCollection = new ArrayList<NetworkInterface>(); 
     for (NetworkInterface networkInterfaceCollectionNetworkInterfaceToAttach : device.getNetworkInterfaceCollection()) { 
      networkInterfaceCollectionNetworkInterfaceToAttach = em.getReference(networkInterfaceCollectionNetworkInterfaceToAttach.getClass(), networkInterfaceCollectionNetworkInterfaceToAttach.getId()); 
      attachedNetworkInterfaceCollection.add(networkInterfaceCollectionNetworkInterfaceToAttach); 
     } 
     device.setNetworkInterfaceCollection(attachedNetworkInterfaceCollection); 
     em.persist(device); 
     for (NetworkInterface networkInterfaceCollectionNetworkInterface : device.getNetworkInterfaceCollection()) { 
      Device oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface = networkInterfaceCollectionNetworkInterface.getDeviceId(); 
      networkInterfaceCollectionNetworkInterface.setDeviceId(device); 
      networkInterfaceCollectionNetworkInterface = em.merge(networkInterfaceCollectionNetworkInterface); 
      if (oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface != null) { 
       oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface.getNetworkInterfaceCollection().remove(networkInterfaceCollectionNetworkInterface); 
       oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface = em.merge(oldDeviceIdOfNetworkInterfaceCollectionNetworkInterface); 
      } 
     } 
     em.getTransaction().commit(); 
    } finally { 
     if (em != null) { 
      em.close(); 
     } 
    } 
} 

回答

21

我终于理解了坚持一对多的逻辑背后的逻辑。这个过程是:

  1. 创建父类
  2. 坚持它
  3. 创建子类
  4. 与之相关联孩子的父母
  5. 坚持儿童(父集合更新)

带代码:

public class Main { 
    public static void main(String[] args) { 
     EntityManagerFactory emf = Persistence.createEntityManagerFactory("wifi-dbPU"); 
     DeviceJpaController deviceController = new DeviceJpaController(emf); 
     NetworkInterfaceJpaController netController = new NetworkInterfaceJpaController(emf); 

     Device device = new Device("laptop");     // 1 
     deviceController.create(device);      // 2 

     NetworkInterface net = new NetworkInterface("eth0"); // 3 
     net.setDeviceId(device.getId());      // 4 
     netController.create(net);       // 5 
     // The parent collection is updated by the above create  
    } 
} 
现在

,我能找到一个设备(ID为举例),我可以得到它的所有使用

Collection<NetworkInterface> netCollection = device.getNetworkInterfaceCollection() 

在我张贴在有问题的设备实体类的孩子,就没有必要对方法addNetworkInterfaceremoveNetwokrInterface

+1

根据我读的书。 cascade = CascadeType.Persist'应该保留所有的关系,并且你只需要更新一个实体,JPA将浏览关系并更新关联的实体。但是......我无法让它工作......无论如何, –

2

这是收集数据成员的已知行为。 最简单的解决方案是修改你的集合获取器来延迟创建集合。

@XmlTransient 
public Collection<NetworkInterface> getNetworkInterfaceCollection() { 
    if (networkInterfaceCollection == null) { 
     networkInterfaceCollection = new Some_Collection_Type<NetworkInterface>(); 
    } 
    return networkInterfaceCollection; 
} 

此外,请记住只能通过getter方法引用此数据成员。

+0

这是在'DeviceJpaController'做来告诉你@ManyToOne关系,它是允许更新mytable的这样的更新=真正的创造。看看是否在开始。如果集合为空,则它创建一个新的并使用'device.setNetworkInterfaceCollection'方法设置它。我尝试了你的建议,并得到:'IllegalArgumentException:为这个查找操作错误地提供了一个null PK的实例。' –

4

@Dima K对他们说的是正确的。当你这样做:

Device device = new Device("laptop"); 
    NetworkInterface net = new NetworkInterface("eth0"); 

    device.getNetworkInterfaceCollection().add(net); 
    deviceController.create(device); 

设备中的集合尚未初始化,所以你试图添加到它时,你会得到一个NPE。在你Device类,声明你Collection的时候,你也可以将其初始化:

private Collection<NetworkInterface> networkInterfaceCollection = new CollectionType<>(); 

至于坚持,你的假设是正确的,但我认为执行是错误的。在创建设备时,请立即使用JPA使其持久(在需要的地方进行事务管理)。

Device device = new Device("laptop"); 
getEntityManager().persist(device); 

做同样的的NetworkInterface:

NetworkInterface net = new NetworkInterface("eth0"); 
getEntityManager().persist(net); 

现在因为两者的实体仍然存在,您可以添加一个到另一个。

device.getNetworkInterfaceCollection().add(net);

JPA应该照顾剩下的,您无需调用任何其他存在。

+1

你对Collection的初始化都是正确的。但这是我的问题。当我需要的是创建集合时,为什么我必须坚持两个实体。 JPA不应该坚持集合中的所有实体并将它们与父类关联吗? –

+0

JPA知道实体以及它们处于什么状态。因此,创建设备实体和网络接口实体就是这样做的。 JPA将知道两个实体。这是你的责任,告诉它两者之间的关系。我理解你的意思,如果你坚持使用该设备并在其后添加元素,JPA应该坚持这些。如果在您持久保存设备时收集该集合(反之亦然),那么该设备及其集合元素将被保留。 –

0

这个异常意味着你试图找到一个实体(可能是通过em.getReference())还没有被保存。 你不能在没有PK的实体上使用em.getReference()或em.find()。

+0

你说得对。它试图在em.getReference()调用中找到网络的Id。该ID为空,因为该实体尚未被保存。请参阅@Sotirios Delimanolis帖子的评论。 –

-1

为了在@OneToMany关系中启用保存功能,例如

@OneToMany(mappedBy="myTable", cascade=CascadeType.ALL) 
private List<item> items; 

然后,你必须

@ManyToOne @JoinColumn(name="fk_myTable", nullable = false, updatable = true, insertable = true) 
+0

'updatable = true,insertable = true'是默认值,所以这不能解决问题。 –