Hibernate database mapping - java

Firstly, I am somewhat new with Hibernate. To get to know the technology I am using it in a project. I am trying to map the following database:
Campaign
campaignId(+)
name
Promotion
campaignId(+)
discount(+)
product
message
I've indicated the primary key in both cases with a (+). The 'campaignId' in Promotion is a foreign key to Campaign to model the 1:m mapping (A Campaign has many Promotions). Using annotations I am stuck on how to do this.
I do not really want to add a promotionId in the Promotion table as it makes working with the data cumbersome. This of course, makes the bridging table a bit tricky. I also have problems working with a foreign key that is also part of the primary key.
Is a mapping for this possible at all?
Ok, I got it working. Sort of. Have to check if persistence actually work. I did the following:
#Entity
#Table(name = "CAMPAIGNS")
#Audited
public class CampaignEntity {
private int campaignId;
private String name;
private List<PromotionEntity> promotions;
public CampaignEntity(int campaignId, String name) {
this.campaignId = campaignId;
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cmp_id")
public int getCampaignId() {
return campaignId;
}
public void setCampaignId(int campaignId) {
this.campaignId = campaignId;
}
// Campaign name here... left out to save space
#OneToMany
#JoinColumn(name = "cmp_id")
public List<PromotionEntity> getPromotions() {
return promotions;
}
public void setPromotions(List<PromotionEntity> promotions) {
this.promotions = promotions;
}
}
Promotion is a vanilla mapping (not using embedded after all), with the fields: campaignId, discount, message. (It also does not have a #ManyToOne annotation.)
Does that make sense?
Lastly, and this will be first prize: as you can see I'm using Envers to audit the whole thing. The above creates a rather ugly "CampaignEntity_PromotionEntity_AUD" table. I understand that it is needed, but how can I rename it to CAMPAIGN_PROMOTION_AUD rather?
Thanks guys!
I got an answer on a lonely website deeply hidden away in far-corners of the Hibernate's Jira error tracking website: https://hibernate.onjira.com/browse/HHH-3729.
The answer is to use #AuditJoinTable(name = "CAMPAIGN_PROMOTION_AUD") of course.

This is a basic example of a one-to-many relationship and its inverse.
public class Campaign
{
#OneToMany(mappedBy = "campaign)
private List<Promotion> promotions;
}
public class Promotion
{
#ManyToOne
private Campaign campaign;
}

You can use an EmbeddedId to create a multi-field PK.
Remove the PK fields from Promotion
Create a separate entity, say PromotionPK, without any annotations except for #Column on the PK fields
In Promotion, include that PK class as field, annotating it using #EmbeddedId, with getters and setters
The FK mapping is as Wouter indicated.

This is what I am now using. It works well and Hibernate handles the PKs of the Promotions for me. Thanks again.
#Entity
#Table(name = "CAMPAIGNS")
#Audited
public class CampaignEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer campaignId;
#Column(name = "name", nullable = false, unique = true)
private String campaignName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "CAMPAIGN_PROMOTIONS",
joinColumns = { #JoinColumn(name = "campaign_id") },
inverseJoinColumns = { #JoinColumn(name = "promotion_id") })
private Set<PromotionEntity> promotions;
...
}
and then, PromotionEntity:
#Entity
#Table(name = "PROMOTIONS")
#Audited
public class PromotionEntity implements Comparable<PromotionEntity> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "discount", nullable = false)
private Integer discount;
#Column(name = "message", nullable = false)
private String message;
...
}
I also prefer annotating the fields rather than the getters as it is more compact and reads easier.

Related

JPA one to one mapping creates multiple query when child entity is not found

I have a parent entity 'contracts' that has a one-to-one relation with another entity 'child-contract'. the interesting thing is that the mapping field ('contract_number')id not a primary key-foreign key but is rather a unique field in both the tables. Also it is possible for a contracts to not have any child contract altogether. With this configuration I have observed hibernate to generate 1 additional query every time a contracts does not have a child-contract. I filed this behavior very strange. Is there a way to stop these unnecessary query generation or have I got something wrong.
below is a piece of my code configuration.
#Data
#Entity
#Table(name = "contracts")
public class Contracts implements Serializable {
#Id
#JsonIgnore
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
#OneToOne(fetch=FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JsonProperty("crm_contracts")
#JoinColumn(name = "contract_number", referencedColumnName = "contract_number")
private ChildContract childContract ;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "child_contract")
#BatchSize(size=1000)
public class ChildContract implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty("id")
#Column(name = "id")
private String id;
#JsonProperty("contract_number")
#Column(name = "contract_number")
private String contractNumber;
}
Please help.
Thank-you
You can use NamedEntityGraph to solve multiple query problem.
#NamedEntityGraph(name = "graph.Contracts.CRMContracts", attributeNodes = {
#NamedAttributeNode(value = "crmContract") })
Use this on your repository method as
#EntityGraph(value = "graph.Contracts.CRMContracts", type = EntityGraphType.FETCH)
// Your repo method in repository

JPA: How to handle versioned entities?

I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.

How to use entity field in Hibernate #Formula

Many times I'm using #Formula in my entities. But always it was a simple query or stored procedure with parameter which I can take as filed from table. Now I need to user some property from related object. But I see exception when try to get object from DB. Please see an example below
#Entity
#Table(name = "MINISTRY")
public class Ministry {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
// unnecessary code
}
#Entity
#Table(name = "DEPARTMENT")
public class Department {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "DEP_NAME")
private String departmentName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "MINISTRY_ID")
private Ministry ministry;
// unnecessary code
}
#Entity
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPARTMENT_ID")
private Department department;
#Formula("test_package.calc_something(department.ministry.id)")
private BigDecimal someMetric;
// unnecessary code
}
How I should use entity prop in #Formula.
I don't want to write something like
select d.ministry.id from Department d ...
If you read the JavaDoc of Formula you will see:
The formula has to be a valid SQL fragment
So you will have to use SQL like:
#Formula("test_package.calc_something("
+ "select DEP.MINISTRY_ID from DEPARTMENT DEP where DEP.ID = DEPARTMENT_ID"
+ ")")
private BigDecimal someMetric;
The only thing that is modified by Hibernate in the fragment before writing it to SQL: It will add the table alias to your columns (as you can't predict that). I mention that, as only a rudimentary SQL parser is used for that, which will insert the alias at wrong positions for more complex fragments.
A remark about performance: The formula is executed for every Department entity that you load, even if you only want to use the attribute for sorting or filtering (just guessing from the name of the attribute) - unless you use #Basic(fetch = FetchType.LAZY) and turn bytecode instrumentation on (or emulate that with FieldHandled).

Hibernate Exception: Trying to map a Collection OneToMany without JoinTable

I'm trying to map an entity the contains a Collection without use JoinTable.
The exception that I keep having is:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: Foo.collection[java.lang.String]
I know that it works with JoinTable, but then I get 2 tables while I want only one table. I know that if it worked then I'd have the "name" many times in the table (one for each element in the collection).
I looked in the documentation (http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#entity-mapping-association see section: 2.2.5.3.1.2. Unidirectional and http://docs.oracle.com/javaee/6/api/javax/persistence/OneToMany.html)and many examples but yet I cant find what I'm doing wrong :(
I also found many questions on this issue where the common answer is to use JoinTable, but this is not the solution I'm looking for.
I tries playing with it, like using #JoinColumn(name = "ID", referencedColumnName="NAME") but no good..
I'm using Java6, jpa2, hibernate 3.5.4. and MySql
Any advice?
Thanks in advance,
Baba
#Entity
#Table(name = "T1")
public class Foo {
private long id;
private String name;
protected Collection<String> collection;
#Id
#GeneratedValue
#Column(name = "ID")
public long getId() {
return id;
}
#Basic
#Column(name = "NAME", nullable = false, unique = true)
public String getName() {
return name;
}
#Column(name = "COLLECTION")
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "ID")
public Collection<String> getCollection() {
return collection;
}
/*
* setters...
*/
}
This is in Hibernate docs.
#ElementCollection
#CollectionTable(name="Nicknames", joinColumns=#JoinColumn(name="user_id"))
#Column(name="nickname")
public Set<String> getNicknames() { ... }

JPA Compound key with #EmbeddedId

In a legacy database, I have three tables: Users, Workgroups, and UsersWorkgroup. UsersWorkgroup stores what role a user has in a workgroup.
Here are the relevant code snippets:
#Entity
#Table(name = "users_workgroup")
public class UsersWorkgroup implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected UsersWorkgroupPK usersWorkgroupPK;
#JoinColumn(name = "idworkgroup", referencedColumnName = "idworkgroup")
#ManyToOne(optional = false)
private Workgroup workgroup;
#JoinColumn(name = "user_name", referencedColumnName = "user_name")
#ManyToOne(optional = false)
private Users users;
#Column(name = "role")
private Integer role;
#Embeddable
public class UsersWorkgroupPK implements Serializable {
#Basic(optional = false)
#Column(name = "idworkgroup", insertable=false, updatable=false)
private int idworkgroup;
#Basic(optional = false)
#Column(name = "user_name", insertable=false, updatable=false)
private String userName;
#Entity
#Table(name = "workgroup")
public class Workgroup implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idworkgroup")
private Integer idworkgroup;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "idworkgroup")
private Collection<UsersWorkgroup> usersWorkgroupCollection;
And of course, problem is, it doesn't work.
Currently I get this exception:
Exception Description: An incompatible
mapping has been encountered between
[class entity.Workgroup] and [class
entity.UsersWorkgroup]. This usually
occurs when the cardinality of a
mapping does not correspond with the
cardinality of its backpointer.
Which I don't understand since OneToMany should match ManyToOne... Or is it a ManyToMany relationship? If I switch to #ManyToMany, I get this:
Exception Description: The target
entity of the relationship attribute
[workgroup] on the class [class
com.ericsson.rsg.ejb.entity.UsersWorkgroup]
cannot be determined. When not using
generics, ensure the target entity is
defined on the relationship mapping.
I'm trying to understand compound keys (embedded), but all the examples I could find have only simple columns that are not foreign keys (but that's the whole point of a compound key, isn't it?). Can the UsersWorkgroup table secretly be a join table?
Should I declare the PK class as a strict POJO class? Or should I put the #JoinColumn annotations in the PK class? How do I refer to the columns within the compound key from another table? Should I initialize the PK object in the refering class constructor, or is it not necessary?
I feel stuck completely.
First of all, I think your relation is a Many To Many, as a user can be in many groups, and a group can have many users (or I would assume so).
Second, as far as I know you have to reference both id_workgroup and user_name as JoinColumns, because they are part of the PK and a unit, so both should be referenced.
Also, I see the "equals" and "hashCode" methods missing from your embedded PK, as well as the getters/setters. I believe they are mandatory.
Your mapping looks fine except for mappedBy - it should be a property name, not a column name:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "workgroup")
private Collection<UsersWorkgroup> usersWorkgroupCollection;

Categories