I just refactor a Project to use Hibernate (4.2.4.Final) with Inheritance. But I got trouble with ManyToMany annotation.
I have a base File Class like this:
#Entity
#Table(name = "file")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "descriminator", length = 25)
public abstract class File {
#Id
#Column(name = "id", unique = true, nullable = false, length = 256)
private String id;
}
and a special Inheritance class like this:
#Entity
#DiscriminatorValue("ISSUE_HISTORY_ATTACHMENT")
#Data
public class IssueHistoryAttachment extends File {
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "issue_history_attachment", joinColumns = {
#JoinColumn(name = "attachment_id", nullable = false, unique = true) }, inverseJoinColumns = {
#JoinColumn(name = "issue_history_id", nullable = false)})
private IssueHistory history;
}
This IssueHistoryAttachment Class is also referenced in my IssueHistory Class.
#Entity
#Table(name = "issue_history")
#TableGenerator(name="tg", table="hibernate_sequences",pkColumnName="sequence_name", valueColumnName="sequence_next_hi_value", allocationSize=1)
public class IssueHistory implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "tg")
#Column(name = "id", unique = true, nullable = false)
private int id;
// some other fields
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "issue_history_attachment", joinColumns = {
#JoinColumn(name = "issue_history_id", nullable = false)
}, inverseJoinColumns = {
#JoinColumn(name = "attachment_id", nullable = false, unique = true)
})
private Set<IssueHistoryAttachment> attachments = new HashSet<IssueHistoryAttachment>();
}
When i now store a IssueHistory Instance with two Attachments, all this fields are correctly saved in my database.
I got 2 new entries in the file table, one new entry in the *issue_history* table and two correct entries in the relation table *issue_history_attachment*.
So at this points all thinks are looking fine. But when I try to read the Values Attachment Set in the IssueHistory Instance only contains one element instead of two like stored in the database.
Any suggestions how to solve this?
I just found the source of the Problem.
It was a missing/wrong equals method. :-)
I can't comment yes so I have to make an answer.
I see one problem in your code (or maybe I don't understand it):
In IssueHistory you are using #ManyToMany to IssueHistoryAttachment but in IssueHistoryAttachment you are using #ManyToOne.
In my opinion it is the reason of your problem.
Related
I am using PostgreSQL 12.11, JPA 3.1.0, and Hibernate 5.6.10. This might become important because I am doing things that apparently do not work with JPA 2.0.
My goal is to add an attribute to a many-to-many relationship. I found this posting. #Mikko Maunu states that "There is no concept of having additional persistent attribute in relation in JPA (2.0)." To me, this sounds like what I want to do is not possible. However, the answer is rather old and might not be complete anymore.
Beside the time gap and the version gap, this is, in my opinion, a new question because I am doing something that is probably questionable and not part of the original thread.
What I did is this:
Create a #ManyToMany relationship in JPA and specify a #JoinTable.
Manually define an entity with identical table name to the table specified in 1. For this table, I chose a composite primary key using #IdClass. I also added my attribute.
Inside one of the n:m-connected entities, create a #OneToMany relationship to the connection-table-entity created in 2. However, I did not create a corresponding #ManyToOne relationship as that would have created an error.
As a result, I can access the original entities and their relation as many-to-many, but also the relation itself, which is not an entity in the original ERM, but it is for JPA. First tests show this seems to be working.
I am aware, however, that I basically access the same part of the persistence (the PostgreSQL database) through two different ways at the same time.
Now my questions are:
Is this a valid way to do it? Or will I get in bad trouble at one point?
Is there a situation where I will need to refresh to prevent trouble?
Is this something new in JPA > 2.0, or just an extension to the original answer?
This should help.
Here is how I do it:
#Entity
#Table(name = "person", schema = "crm")
public final class Person implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person", orphanRemoval = true)
private Set<PersonEmail> emails = new HashSet<>();
}
#Entity
#Table(name = "email", schema = "crm")
public final class Email implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#Column(name = "email", nullable = false, length = 64, columnDefinition = "varchar(64)")
private String localPart;
#Column(name = "domain", nullable = false, length = 255, columnDefinition = "varchar(255)")
private String domain;
}
#Entity
#Table(name = "person_email", schema = "crm")
public final class PersonEmail implements Serializable {
#EmbeddedId
private PersonEmailId id;
// The mapped objects are fetched lazily.
// This is a choice.
#ToString.Exclude
#MapsId("personId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Person person;
#ToString.Exclude
#MapsId("emailId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Email email;
// Here's an extra column.
#Column(name = "type", nullable = false, columnDefinition = "email_type_t")
#Convert(converter = EmailType.EmailTypeConverter.class)
private EmailType type;
public final void setPerson(final Person person) {
this.person = person;
id.setPersonId(this.person.getId());
}
public final void setEmail(final Email email) {
this.email = email;
id.setEmailId(this.email.getId());
}
#Embeddable
public static final class PersonEmailId implements Serializable {
#Column(name = "person_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long personId;
#Column(name = "email_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long emailId;
}
I have following entities and need to retrieve a list of names of all stores that are in a specific group and have branches in a specific city. Majority of tutorials and articles that Ive found are related to creating this type of relationships but none of them is about retrieval!
I changed the criteria for many times but Hibernate shows different errors for each. The commented parts of the code are those that I tried and the respective thrown exception is also written in front of each.
Entities
#Entity
public class Store {
#Id
String id;
String name;
#JoinTable(name = "store_groups", joinColumns = { #JoinColumn(name = "id", nullable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "code", nullable = false, updatable = false) })
private Set<Group> groups = new HashSet<Group>(0);
private Set<StoreAddress> storeAddresses = new HashSet<StoreAddress>(0);
....
}
#Entity
public class Group {
#Id
String code;
#Column(nullable = false, unique = true)
String name;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
Set<Store> storees = new HashSet<Store>(0);
}
#Entity
#Table(name = "StoreAddresses")
#AssociationOverrides({
#AssociationOverride(name = "muJoinTable.store", joinColumns = #JoinColumn(name = "id", nullable = false)),
#AssociationOverride(name = "myJoinTable.city", joinColumns = #JoinColumn(name = "cityCode", nullable = false)) })
public class StoreAddress {
#EmbeddedId
private StoreCitysId myJoinTable = new StoreCitysId();
...
}
#Embeddable
public class StoreCitysId {
#ManyToOne
private Store store;
#ManyToOne
private City city;
}
#Entity
public class City {
#Id
short code;
#Column(nullable = false)
String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "myJoinTable.city")
private Set<StoreAddress> storeAddresses = new HashSet<StoreAddress>(
0);
}
Criteria
List<String> storees = (List<String>) sessionFactory
.getCurrentSession()
.createCriteria(Store.class)
.setProjection(
Projections.property("name").as(
"storeName"))
.createAlias("groups", "group")
.createAlias("storeAddresses", "address")
// .createAlias("address.myJoinTable.city", "city")
// .createAlias("address.myJoinTable", "myJoinTable")
// .createAlias("myJoinTable.city", "city") Error: Criteria objects cannot be created directly on components
.setFetchMode("group", FetchMode.JOIN)
.add(Restrictions.ilike("group.code", store))
.add(Restrictions.eq("address.myJoinTable.cityCode",
1)).list(); //city.code -> Error: could not resolve property: cityCode of:com.example.entity.StoreAddress address.myJoinTable.cityCode could not resolve property: myJoinTable.cityCode of:com.example.entity.StoreAddress
Your criterion Restrictions.eq("address.myJoinTable.cityCode", 1) doesn't reference a property but the name of the column. You could instead use address.myJoinTable.city and set the value to session.load(City.class, 1) making Restrictions.eq("address.myJoinTable.city", session.load(City.class, 1))
And this:
.createAlias("address.myJoinTable", "myJoinTable")
.createAlias("myJoinTable.city", "city")
Should be:
.createAlias("address.myJoinTable.city", "city")
I have the following scenario:
Base Domain class:
#MappedSuperclass
public class BaseDomain {
#Id
protected UUID id;
}
Media Object class:
#Entity
public class MediaObject extends BaseDomain {
#ManyToMany
#JoinTable(
joinColumns = {
#JoinColumn(name = "BaseDomain_id", referencedColumnName = "id"
}
inverseJoinColumns = {
#JoinColumn(name = "Media_id", referencedColumnName = "id")
}
private List<BaseDomain> holders;
}
"Holder" A:
#Entity
public class A extends BaseDomain {
#ManyToMany
private List<MediaObject> media;
}
"Holder" B:
#Entity
public class B extends BaseDomain {
#ManyToMany
private List<MediaObject> media;
}
What I want to achieve is, to store a MediaObject and multiple entities may "hold" this object. My approach would be a using a JoinTable that stores the relation between the MediaObject and an arbitrary BaseDomain object (as above). The issue I'm facing is that the persistence provider (in my case Hibernate) would not be able to decide which actual table to join.
I'm thinking about using a unidirectional #OneToMany which is possible in JPA 2.1.
However, I want to ask, if there are some kind of best practices to approach such a situation.
Following snippet is used by me in production environement, it implements ManyToMany assiciation mapping for Hibernate.
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "printed_mails_logo",
joinColumns = {
#JoinColumn(name = "mails_id", nullable = false, updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "logo_id", nullable = false, updatable = false) })
private Set<Logo> printedLogos;
printer_mails_logo is additinal associative table in database.
#JoinColumn(name='x') is the actual name of column in associative table.
This works well for me. I can fetch without no problem all logos that has been printed already.
I have a parent class:
#MappedSuperclass
public class BaseText implements Serializable {
#Column(name = "LOCALE_CODE", nullable = false)
private String localeCode;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Index
#Column(name = "LOCALIZED_TEXT_ID", nullable = false)
#ForeignKey
private LocalizedText localizedText;
//getters and setters
}
And one of the sub classes:
#Entity
#Table(name = "ASSESSMENT_TEXT")
#AttributeOverride(name = "localeCode", column = #Column(name = "LOCALE_CODE"))
#AssociationOverride(name = "localizedText", joinColumns = #JoinColumn(name = "LOCALIZED_TEXT_ID"))
public class AssessmentText extends BaseText {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Index
#Column(name = "ASSESSMENT_ID", nullable = false)
#ForeignKey
private Assessment assessment;
//Getters and setters.
}
When I am trying to persist an object I get the following error:
org.apache.openjpa.persistence.ArgumentException: Superclass field "java.lang.Object.localizedText" is mapped in the metadata for subclass "com.my.com.AssessmentText", but is not a persistent field.
What is causing this and how to solve it?
I am using embedded derby database in JUnit and the JPA implementation is OpenJPA.
I am not sure that it is the solution, but I miss an #Inheritance annotation from AssessmentText:
#Entity
#Table(name = "ASSESSMENT_TEXT")
#Inheritance(strategy=InheritanceType.JOINED)
#AttributeOverride(name = "localeCode", column = #Column(name = "LOCALE_CODE"))
#AssociationOverride(name = "localizedText",
joinColumns = #JoinColumn(name = "LOCALIZED_TEXT_ID"))
public class AssessmentText extends BaseText {
I had the same issue, and solved it by adding the MappedSuperClass to the persistence.xml.
I know this is also in the comments somewhere, but the useful one was hidden and I think this should be an answer, not a comment to the question.
Im stuck with this problem. The database schema is provided by someone else so I cant simply change names. I tried add everywhere proper annotations, maybe I'm missing something (obvious)?
Here is my full mapping (quite many classess), I'll ommit getter/setters.
The problem is when hibernate is trying to get all List<ControlRuleAttrib> controlRuleAttribs
Controle Rule
#Entity
#Table(name = "CONTROL_RULE")
public class ControlRule implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CONTROL_RULE_ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#Cascade(CascadeType.ALL)
#JoinColumn(name = "CONTROL_RULE_TYPE_ID")
#ForeignKey(name = "CONTROL_RULE_TYPE_ID")
private ControlRuleType controlRuleType;
#Column(name = "JOB_NM")
private String jobname;
#Column(name = "LIBRARY_NM")
private String libraryname;
#Column(name = "TABLE_NM")
private String tablename;
#Column(name = "COLUMN_NM")
private String columnname;
#OneToMany(fetch = FetchType.LAZY)
#Cascade(CascadeType.ALL)
#JoinTable(name = "CONTROL_RULE_ATTRIB", joinColumns = {
#JoinColumn(name = "CONTROL_RULE_ID", nullable = false, updatable = false)
})
private List < ControlRuleAttrib > controlRuleAttribs;
}
ControlRuleAttrib
#Table(name = "CONTROL_RULE_ATTRIB")
#Entity
public class ControlRuleAttrib {
#EmbeddedId
private ControlRuleAttribPK controlRuleAttribPK;
#Column(name = "ATTRIBUTE_VALUE")
private String attributeValue;
}
ControleRuleAttribPK
Question here is, is it possible to somehow get Entity ControlRuleAttribType from ControlRuleAttrib? As you can see below ControlRuleAttribTypeId is the id of ControleRuleAttribType. I'd like to get whole object isteand of integer.
#Embeddable
public class ControlRuleAttribPK implements Serializable {
#Column(name = "CONTROL_RULE_ID")
private Long controlRuleId;
#Column(name = "ATTRIBUTE_SEQ_NUM")
private Integer attributeSeqNum;
#Column(name = "CONTROL_RULE_ATTRIB_TYPE_ID")
private Integer controlRuleAttribTypeId;
}
ControleRuleAttribType
#Entity
#Table(name = "CONTROL_RULE_ATTRIB_TYPE")
public class ControlRuleAttribType implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CONTROL_RULE_ATTRIB_TYPE_ID")
private Integer id;
#Column(name = "CONTROL_RULE_ATTRIB_TYPE_NM")
private String typename;
#Column(name = "CONTROL_RULE_ATTRIB_TYPE_DESC")
private String typedesc;
#ManyToOne(fetch = FetchType.LAZY)
#Cascade(CascadeType.ALL)
#JoinColumn(name = "CONTROL_RULE_TYPE_ID")
#ForeignKey(name = "CONTROL_RULE_TYPE_ID")
private ControlRuleType controlruletype;
}
ControleRuleType
#Entity
#Table(name = "CONTROL_RULE_TYPE")
public class ControlRuleType implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CONTROL_RULE_TYPE_ID")
private Integer id;
#Column(name = "CONTROL_RULE_TYPE_NM")
private String typename;
#Column(name = "CONTROL_RULE_TYPE_DESC")
private String typedesc;
}
EDIT
Here is stacktrace:
https://gist.github.com/a30dd9ce534d96bb9a97
As you'll find out, it fails here:
at
com.execon.controllers.main.MainPageController.getMainPage(MainPageController.java:33)
[classes:]
and this is it:
List<ControlRule> list = SessionFactoryUtils.openSession(
sessionFactory ).createQuery( "from ControlRule" ).list();
System.out.println( list );
every object which mapping I added, has toString() method declared like this:
#Override
public String toString()
{
String s = "ControlRule{";
s += "id=" + id.toString();
s += ", controlRuleType=" + controlRuleType;
s += ", jobname='" + jobname + '\'';
s += ", libraryname='" + libraryname + '\'';
s += ", tablename='" + tablename + '\'';
s += ", columnname='" + columnname + '\'';
s += ", controlRuleAttribs=" + controlRuleAttribs;
s += '}';
return s;
}
And hibernate request:
https://gist.github.com/c8584113522757a4e0d8/4f31dc03e7e842eef693fa7ba928e19d27b3ca26
Help please :)
EDIT 2
Well after reading #Jens answer, I did some changes in the code. First I did as you wrote and it gave error:
org.hibernate.AnnotationException: A Foreign key refering
com.execon.models.controlrules.ControlRuleAttrib from
com.execon.models.controlrules.ControlRule has the wrong number of
column. should be 3
I guess this is right, as I have composite primary key.
Then I tried it this way:
#OneToMany(fetch = FetchType.LAZY)
#Cascade(CascadeType.ALL)
#JoinTable(name = "CONTROL_RULE_ATTRIB",
joinColumns = {
#JoinColumn(name = "CONTROL_RULE_ID", nullable = false, updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "CONTROL_RULE_ID", nullable = false, updatable = false),
#JoinColumn(name = "CONTROL_RULE_ATTRIB_TYPE_ID", nullable = false, updatable = false),
#JoinColumn(name = "ATTRIBUTE_SEQ_NUM", nullable = false, updatable = false)
})
private List<ControlRuleAttrib> controlRuleAttribs;
Quite close but it gives me the following exception:
Repeated column in mapping for collection..
So finally I removed
joinColumns =
{
#JoinColumn(name = "CONTROL_RULE_ID", nullable = false, updatable = false)
}
And everything compiled except that when I try to reach collection, Hibernate is doing following query:
https://gist.github.com/c88684392f0b7a62bea5
The last line, is controlrul0_.CONTROL_RULE_CONTROL_RULE_ID=? while it should be controlrul0_.CONTROL_RULE_ID=?.
Is there anyway I can make it work? :/
After struggling with this for last few hours I finally make it working within my project. The thing I did was this:
ControlRule
#OneToMany(fetch = FetchType.LAZY, mappedBy = "controlRuleAttribPK.controlRuleId")
#Cascade(CascadeType.ALL)
private List<ControlRuleAttrib> controlRuleAttribs;
Basically pointing that the collection should use controlRuleId from composite primary key. So far its working great!
I do not clearly understand which identifier is too long, but I can propose that you can try to change #Id annotated field types from Integer to Long. And it will be great to get some detailed info (stack trace etc.) about your problem.
The problem is the column
CONTROL_RULE_ATTRIB.controlRuleAttribs_CONTROL_RULE_ATTRIB_TYPE_ID
Which is so long it can't possibly exist in your database. I don't see in the question any hint how it is actually named, but I am assuming the proper name is
CONTROL_RULE_ATTRIB.CONTROL_RULE_ATTRIB_TYPE_ID
So the question is: Why is hibernate trying to access this column. The table it is in is a mapping table which is mapped in hibernate as:
#OneToMany(fetch = FetchType.LAZY)
#Cascade(CascadeType.ALL)
#JoinTable(name = "CONTROL_RULE_ATTRIB", joinColumns = {#JoinColumn(name = "CONTROL_RULE_ID", nullable = false, updatable = false)})
private List<ControlRuleAttrib> controlRuleAttribs;
If you look at the #JoinTable annotation you'll notice that it defines the table name as it is used and a join column. But a mapping table has two sets of join columns. The second on is not specified. Therefore the NamingStrategy you configured or if you didn't configured any the default is used. Just specify the other set of join columns using the attribute inverseJoinColumns and all should be fine.
The complete annotation should look like:
#JoinTable(name = "CONTROL_RULE_ATTRIB", joinColumns = {#JoinColumn(name = "CONTROL_RULE_ID", nullable = false, updatable = false)}, inverseJoinColumns = {#JoinColumn(name = "CONTROL_RULE_ATTRIB_TYPE_ID")})
Note: I have no idea if you need any of the nullable, updatable stuff on that column as well.
See also the documentation of the JoinTable annotation