我正在尝试关注JPA tutorial并使用ElementCollection
记录员工电话号码:
PHONE (table)
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
我需要的是这样的课程:
@Entity
@Table(name="Phones")
public class PhoneId {
@Id
@Column(name="owner_id")
long owner_id;
@Embedded
List<Phone> phones;
}
将每个人的电话号码存储在一个集合中。
我遵循教程代码:
@Entity
@Table(name="Phones")
public class PhoneId {
@Id
@Column(name="owner_id")
long owner_id;
@ElementCollection
@CollectionTable(
name="Phones",
joinColumns=@JoinColumn(name="owner_id")
)
List<Phone> phones = new ArrayList<Phone>();
}
@Embeddable
class Phone {
@Column(name="type")
String type = "";
@Column(name="number")
String number = "";
public Phone () {}
public Phone (String type, String number)
{ this.type = type; this.number = number; }
}
略有不同,我只保留一张桌子。我尝试使用以下代码将记录添加到此表中:
public static void main (String[] args) {
EntityManagerFactory entityFactory =
Persistence.createEntityManagerFactory("Tutorial");
EntityManager entityManager = entityFactory.createEntityManager();
// Create new entity
entityManager.getTransaction().begin();
Phone ph = new Phone("home", "001-010-0100");
PhoneId phid = new PhoneId();
phid.phones.add(ph);
entityManager.persist(phid);
entityManager.getTransaction().commit();
entityManager.close();
}
但它不断抛出异常
内部异常:org.postgresql.util.PSQLException:错误:null 列“type”中的值违反非空约束详细信息:失败 row包含(0,null,null)。错误代码:0呼叫:INSERT INTO电话 (owner_id)VALUES(?)bind =&gt; [1参数绑定]查询: InsertObjectQuery(tutorial.Phone1@162e295)
我做错了什么?
答案 0 :(得分:2)
可悲的是,我认为你只保留一张桌子的细微差别就是这里的问题。
查看PhoneId
类的声明(我建议更好地称之为PhoneOwner
或类似的声明):
@Entity
@Table(name="Phones")
public class PhoneId {
当您声明某个类是映射到某个表的实体时,您正在创建一组断言,其中两个在此处特别重要。首先,表中有一行用于实体的每个实例,反之亦然。其次,表中有一列用于实体的每个标量字段,反之亦然。这两者都是对象关系映射思想的核心。
但是,在您的架构中,这些断言都不成立。在您提供的数据中:
OWNER_ID TYPE NUMBER
1 home 792-0001
1 work 494-1234
2 work 892-0005
有两行对应于owner_id
1的实体,违反了第一个断言。列TYPE
和NUMBER
没有映射到实体中的字段,违反了第二个断言。
(很明显,您宣布Phone
班级或phones
字段 - 只是PhoneId
实体)没有错误
因此,当您的JPA提供程序尝试将PhoneId
的实例插入数据库时,它会遇到麻烦。因为TYPE
中的NUMBER
和PhoneId
列没有映射,所以当它为插入生成SQL时,它不包含它们的值。这就是为什么你得到你看到的错误 - 提供者写INSERT INTO Phones (owner_id) VALUES (?)
,PostgreSQL将其视为INSERT INTO Phones (owner_id, type, number) VALUES (?, null, null)
,这被拒绝。
即使您确实设法在此表中插入一行,您也会遇到从中检索对象的麻烦。假设你要求PhoneId
owner_id
的实例1.提供者会写出相当于select * from Phones where owner_id = 1
的SQL,并且它会期望找到一行,它可以映射到一行宾语。但它会找到两行!
我担心,解决方案是使用两个表,一个用于PhoneId
,另一个用于Phone
。 PhoneId
的表格很简单,但是正确操作JPA机器是必要的。
假设您将PhoneId
重命名为PhoneOwner
,表格需要如下所示:
create table PhoneOwner (
owner_id integer primary key
)
create table Phone (
owner_id integer not null references PhoneOwner,
type varchar(255) not null,
number varchar(255) not null,
primary key (owner_id, number)
)
(我已将(owner_id, number)
作为Phone
的主键,假设一个所有者可能拥有多个给定类型的号码,但绝不会在两种类型下记录一个号码如果更好地反映您的域名,您可能更喜欢(owner_id, type)
。)
然后是实体:
@Entity
@Table(name="PhoneOwner")
public class PhoneOwner {
@Id
@Column(name="owner_id")
long id;
@ElementCollection
@CollectionTable(name = "Phone", joinColumns = @JoinColumn(name = "owner_id"))
List<Phone> phones = new ArrayList<Phone>();
}
@Embeddable
class Phone {
@Column(name="type", nullable = false)
String type;
@Column(name="number", nullable = false)
String number;
}
现在,如果您真的不想为PhoneOwner
引入表格,那么您可以使用视图来摆脱它。像这样:
create view PhoneOwner as select distinct owner_id from Phone;
就JPA提供商而言,这是一个表,它将支持读取数据所需的查询。
但是,它不支持插入。如果您需要为当前不在数据库中的所有者添加电话,则需要绕过后面并直接在Phone
中插入一行。不是很好。