I am trying to map the following table
CREATE TABLE Person (
p_id varchar(255) not null,
p_name varchar(255 not null,
p_post_code varchar(12) not null,
primary key (p_id, p_name),
);
Usually when i map an Entity to the above table i would do something like this (for single column primary keys):
private int p_id;
private String p_name;
private String p_post_code;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="p_id")
public Long getPId() {
return p_id;
}
public void setPId(int p_id) {
this.p_id = p_id;
}
#Column(name="p_name")
public String getPname() {
return p_name;
}
public void setPname(String string) {
this.p_name = string;
}
#Column(name="p_post_code")
public String getPostCode() {
return p_post_code;
}
public void setPostCode(String string) {
this.p_post_code = string;
}
The above works if the primary key is a single column (i.e. p_id) and the value for this column is generated in the database.
How would i modify the above to map it so that both p_id and p_name are the primary key.
Also, how would this work, if the composite key is a foreign key in another table.
I am trying to google for some examples but i cant find a simple example and most seem to be using the XML based configuration.
When using composite keys with JPA you need to use an embedded class as an id.
In your case you would have a person class and a primary key class for person:
#entity
public class Person
{
#EmbeddedId
private PersonPK key;
#Column(name="p_post_code", nullable = false)
private String p_post_code;
//....
}
#Embeddable
public class PersonPK
{
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="p_id");
private Long p_id;
#Column(name="p_name")
private String p_name;
public PersonPK(String name)
{
p_name = name;
}
//....
}
Using a class for the person's name (so the name is also a foreign key):
#entity
public class Person
{
#EmbeddedId
private PersonPK key;
#MapsId(value="p_name_id")
#ManyToOne
#JoinColumn(name = "p_name_id", referencedColumnName = "id")
private Name p_name;
#Column(name="p_post_code", nullable = false)
private String p_post_code;
//....
}
#Embeddable
public class PersonPK
{
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="p_id");
private Long p_id;
#Column(name="p_name_id")
private Long p_name_id;
public PersonPK(Name name)
{
p_name_id = name.getId();
}
//....
}
#Entity
public class Name
{
#Id
#GeneratedValue(some generation strategy here)
#Column(name="id")
private Long id;
#Column(name="name")
private String name;
//....
}
Related
I'm not sure why I'm getting this error. I'm trying to write a test and I'm getting this error on line artistLabelRepository.save(artistLabel1); in ArtistLabelRepositoryTestIT
Can not set com.pandora.label.entity.Label field com.pandora.label.entity.ArtistLabelId.label to java.lang.Long
These are the tables. the tables are already created and populated. I can alter the tables, but would like to not delete them and recreate them.
CREATE TABLE label (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
parent_label_id BIGINT REFERENCES label(id)
)
CREATE INDEX album_uid_idx ON album_label(id);
CREATE TABLE artist_label (
artist_id VARCHAR(60) NOT NULL,
label_id BIGINT NOT NULL,
PRIMARY KEY (artist_id, label_id)
);
CREATE INDEX artist_label_idx ON artist_label(label_id);
ALTER TABLE artist_label ADD CONSTRAINT artist_label_fk FOREIGN KEY (label_id) REFERENCES label(id);
these are the Entities
class ArtistLabelId implements Serializable {
private String artistId;
private Label label;
public ArtistLabelId() {
// no arg constructor for JPA
}
public ArtistLabelId(String artistId, Label label) {
this.artistId = artistId;
this.label = label;
}
// setters and getters
}
#Entity
#IdClass(ArtistLabelId.class)
#Table(name="artist_label",
indexes = {#Index(name = "artist_label_idx", columnList = "artist_id")})
public class ArtistLabel {
#Id
#Column (name = "artist_id")
private String artistId;
#Id
#ManyToOne
#JoinColumn(name = "label_id")
private Label label;
public ArtistLabel() {
// no arg constructor for JPA
}
public ArtistLabel(String artistId, Label label) {
this.artistId = artistId;
this.label = label;
}
// setters and getters
}
#Entity
#Table(name="label")
public class Label {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="label_generator")
#SequenceGenerator(name= "label_generator", sequenceName = "label_id_seq", allocationSize = 1)
#Column(name = "id")
private long labelId;
#Column (name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "parent_label_id")
private Label parentLabel; //references parent label
#Column(name = "create_date")
private Timestamp createDate;
#Column(name = "last_modified")
private Timestamp lastModified;
public Label(){
//no-arg constructor for JPA
}
public Label(String name, Label parentLabel){
this.name = name;
this. parentLabel = parentLabel;
}
public Label(long labelId, String name, Label parentLabel){
this.labelId = labelId;
this.name = name;
this.parentLabel = parentLabel;
}
// setters and getters
}
this is the repository
#Repository
public interface ArtistLabelRepository extends CrudRepository<ArtistLabel, Long> {}
and this is the test. i'm trying to save an artistLabel artistLabelRepository.save(artistLabel1);
#ActiveProfiles(value="test")
#SpringBootTest(classes = {Application.class})
public class ArtistLabelRepositoryTestIT {
#Autowired
ArtistLabelRepository artistLabelRepository;
#Autowired
LabelRepository labelRepository;
#Test
public void testFindByLabelId(){
Label label = new Label(123L, "label1", null);
labelRepository.save(label);
ArtistLabel artistLabel1 = new ArtistLabel("artistId1", label);
artistLabelRepository.save(artistLabel1);
}
}
I have to map a composite PK with JPA in an Oracle DB.
I've followed other SO questions with relation to this tutorial but I'm still getting the following error:
java.sql.SQLSyntaxErrorException: ORA-00904: "COMPOSITEI0_"."NAME_1": Invalid Identifier (where NAME_1 relates to the name of one of the columns which are part of the PK)
This is my entity (real names not mentioned for data protection reasons):
#Entity
#Table(schema = "SCHEMA", name = "TABLE")
public class CompositeIdEntity {
#Column(name = "NAME1")
private String name1;
#Column(name = "NAME2")
private String name2;
#Column(name = "NAME3")
private String name3;
#EmbeddedId
CompositePrimaryKeyTableEmbeddable id;
public CompositePrimaryKeyTableEmbeddable getId() {
return this.id;
}
public void setId(CompositePrimaryKeyTableEmbeddable id) {
this.id = id;
}
// other getters and setters
My #Embeddable id class:
#Embeddable
public class CompositePrimaryKeyTableEmbeddable implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Column(name="name1")
private String name1;
#Column(name="name2")
private String name2;
public CompositePrimaryKeyTableEmbeddable() {
super();
}
public CompositePrimaryKeyTableEmbeddable(String name1, String name2) {
this.name1 = name1;
this.name2 = name2;
}
My #Repository:
#Repository
public interface CompositeIdDao extends JpaRepository<CompositeIdEntity, CompositePrimaryKeyTableEmbeddable> {
}
And finally call to the DB, which only returns null because it's just a test to see if it all works together:
public CompositeIdEto saveCompositeId() {
CompositeIdEntity compositeIdEto = new CompositeIdEntity();
compositeIdEto.setname3("New");
compositeIdEto.setId(new CompositePrimaryKeyTableEmbeddable("ERR", "ER"));
this.compositeIdDao.save(compositeIdEto);
return null;
}
It seems you are duplicating the name1 and name2 columns by declaring them once
in the entity itself and later in the embeddable.
You seem to only need the id embeddable and the name3 declaration in the entity:
#Entity
#Table(schema = "SCHEMA", name = "TABLE")
public class CompositeIdEntity {
#EmbeddedId
CompositePrimaryKeyTableEmbeddable id;
#Column(name = "NAME3")
private String name3;
My two tables (in SQL Server):
create table cluster (
id bigint primary key identity(1,1),
name varchar(100)
)
create table cluster_member (
cluster_id bigint,
member_name varchar(100)
)
The table cluster_member doesn't have an id. The column cluster_id is like a foreign key, reference to the id column in cluster table.
I used Hiberate Tools to generate 2 #Entity classes and a #Embeddable class. I added some class variables and #OneToMany and #ManyToOne annotations trying to join the two tables. But I got an error saying:
org.hibernate.MappingException: Foreign key (FK_hk6sas3oycvcljwbjar7p9ky3:cluster_member [cluster_id,member_name])) must have same number of columns as the referenced primary key (cluster [id])
The error message is pretty clear. But I don't know how to fix it. Please help.
Here is my code:
Cluster.java:
#Entity
#Table(name = "cluster" )
public class Cluster implements java.io.Serializable {
private long id;
private String name;
private Set<ClusterMember> members;
#Id
#Column(name = "id", unique = true, nullable = false)
public long getId() {
return this.id;
}
#Column(name = "name", length = 100)
public String getName() {
return this.name;
}
#OneToMany(mappedBy = "id")
public Set<ClusterMember> getMembers() {
return members;
}
}
ClusterMember.java:
#Entity
#Table(name = "cluster_member" )
public class ClusterMember implements java.io.Serializable {
private ClusterMemberId id;
private Cluster cluster;
#EmbeddedId
#AttributeOverrides({ #AttributeOverride(name = "clusterId", column = #Column(name = "cluster_id")),
#AttributeOverride(name = "memberName", column = #Column(name = "member_name", length = 100)) })
public ClusterMemberId getId() {
return this.id;
}
#ManyToOne
#JoinColumn(name = "cluster_id")
public Cluster getCluster() {
return cluster;
}
}
ClusterMemberId.java:
#Embeddable
public class ClusterMemberId implements java.io.Serializable {
private Long clusterId;
private String memberName;
#Column(name = "cluster_id")
public Long getClusterId() {
return this.clusterId;
}
#Column(name = "member_name", length = 100)
public String getMemberName() {
return this.memberName;
}
}
main function:
#SuppressWarnings("deprecation")
public static void main(String[] args) {
sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();
Criteria criteria = session.createCriteria("my.hibernate.table.Cluster");
criteria.add(Restrictions.like("name", "%ABC%"));
#SuppressWarnings("unchecked")
List<Cluster> clusters = criteria.list();
for (Cluster cluster: clusters) {
System.out.println(cluster.toString());
}
tx.commit();
sessionFactory.close();
}
hibernate.cfg.xml
<mapping class="my.hibernate.table.Cluster" />
<mapping class="my.hibernate.table.ClusterMember" />
Try changing this:
#OneToMany(mappedBy = "id")
public Set<ClusterMember> getMembers() {
return members;
}
to
#OneToMany(mappedBy = "cluster")
public Set<ClusterMember> getMembers() {
return members;
}
and add insertable/updatable to false on the associated ManyToOne mapping.
#ManyToOne
#JoinColumn(name = "cluster_id", insertable="false", updatable="false")
public Cluster getCluster() {
return cluster;
}
Because you are not really interested in the ClusterMember.id but in the FK linking back to Cluster.
In Hibernate you cannot use the same column in to different mapping. The "ClusterMember" already uses "cluster_id" for the #Id property, hence if you plan on using for a ManyToOne association, you need to instruct Hibernate to ignore any changes to this end (inserts and updates should be ignored).
Also you can use Hibernate's #MapsId annotation, for composite identifiers with alternate associated mappings.
I'm trying to generate ddl from existing annotated entities using "JPA tools --> Generate Tables from Entities..." in Eclipse Kepler. When I run the task, I get a file with sql scripts to run. The problem is that the order of the columns in the table creation statement fails to comply with the order of the attributes in the class definition.
Example:
#Entity
#Table(name = "news", catalog = "myDatabase")
public class News implements java.io.Serializable {
private long id;
private String newsTitle;
private String newsTitle2;
private String newsText;
private Date created;
#Id
#GeneratedValue
#Column(name = "news_id", unique = true, nullable = false)
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
#Column(name = "news_title")
public String getNewsTitle() {
return this.newsTitle;
}
public void setNewsTitle(String newsTitle) {
this.newsTitle = newsTitle;
}
#Column(name = "news_title2")
public String getNewsTitle2() {
return this.newsTitle2;
}
public void setNewsTitle2(String newsTitle2) {
this.newsTitle2 = newsTitle2;
}
#Lob
#Column(name = "news_text")
public String getNewsText() {
return this.newsText;
}
public void setNewsText(String newsText) {
this.newsText = newsText;
}
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created")
public Date getCreated() {
return this.created;
}
public void setCreated(Date created) {
this.created = created;
}
}
Script:
CREATE TABLE myDatabase.news (
news_id BIGINT NOT NULL UNIQUE, created DATETIME, news_text LONGTEXT,
news_title VARCHAR(255), news_title2 VARCHAR(255), PRIMARY KEY (news_id))
How can I get the scripts with the order of the columns aligned with the java class?
Thank you very much
Stefano
This is not possible due to the abstraction approach in JPA. Theoretically you don't know the kind of database below nor whether the order is important there.
I'm currently struggling with a mapping of one to many using annotations in hibernate, whenever I get an object, the set of items returned from the related table is always null (even when I can see in the database there are corresponding relationships with data). I have some many to one relationships in the same class which are working fine.
Other unrelated code omitted for readability
I have two tables, where a single member, can have 0 or more member membership periods:
CREATE TABLE member (
member_id INT NOT NULL AUTO_INCREMENT
PRIMARY KEY (member_id)
)
CREATE TABLE member_membership_period (
member_membership_period_id INT NOT NULL AUTO_INCREMENT ,
member_id INT NOT NULL ,
test_column VARCHAR(45) NOT NULL ,
PRIMARY KEY (member_membership_period_id) ,
INDEX member_membership_period_member_idx (member_id ASC) ,
CONSTRAINT member_membership_period_member
FOREIGN KEY (member_id)
REFERENCES member (member_id)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
The Member class maps to the member table:
#Entity
#Table(name="member")
public class Member implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "member_id")
private int id;
#OneToMany
#JoinColumn(name = "member_id")
#ForeignKey(name = "member_membership_period_member")
private Set<MemberMembershipPeriod> memberMembershipPeriods = new HashSet<MemberMembershipPeriod>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Set<MemberMembershipPeriod> getMemberMembershipPeriods() {
return memberMembershipPeriods;
}
public void setMemberMembershipPeriods(Set<MemberMembershipPeriod> memberMembershipPeriods) {
this.memberMembershipPeriods = memberMembershipPeriods;
}
}
And the MemberMembershipPeriod class maps to the member_membership_period table
#Entity
#Table(name="member_membership_period")
public class MemberMembershipPeriod implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name = "member_membership_period_id")
private int id;
#Column(name = "test_column")
String testColumn;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTestColumn() {
return testColumn;
}
public void setTestColumn(String testColumn) {
this.testColumn = testColumn;
}
}
My DAO
public class MemberDaoImpl extends AbstractDAO<Member> implements MemberDao {
public MemberDaoImpl(SessionFactory factory) {
super(factory);
}
#Override
public List<Member> getAllMembers() {
Query query = currentSession().createQuery("from Member");
return list(query);
}
#Override
public Member getMemberById(int id) {
return get(id);
}
}
Implementation of get(id) (Part of drop wizards hibernate package)
protected E get(Serializable id) {
return (E) currentSession().get(entityClass, checkNotNull(id));
}
Any help provided would be greatly appreciated, I'm starting to lose the will to live over this!
Other tech being used is DropWizard (which does the hibernate configuration) and MySQL
Try this. It may help you.
#Override
public List<Member> getAllMembers() {
Criteria criteria = currentSession().createCriteria(Member.class,"member");
criteria.createAlias("member.memberMembershipPeriods","period");
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return criteria.list();
}