Hibernate one to one mapping creates a foreign key - java

Background: I load up a mysql instance to run DB tests and I create my schema using Hibernate (spring-data-jpa) and run some tests against the DB. All my Entities are created via annotation and loaded in a spring context.
Tables: I have 2 tables:
MainTable:
- id (PK)
- customerId
...
ThirdParty:
- id (PK)
- customerId (FK: MainTable.customerId)
- serviceId
This is a one-to-one mapping as I don't want to add serviceId to my MainTable.
My entities are setup as follows:
#Table
#Entity
public class MainTable {
#Id
#Column
private String id;
#Column(name = "CUSTOMER_ID")
private String customerId;
#OneToOne
#JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
private ThirdParty thirdParty;
}
#Table
#Entity
public class ThirdParty {
#Id
#Column
private String id;
#Column(name = "CUSTOMER_ID")
private String customerId;
#Column(name = "SERVICE_ID")
private String serviceId;
}
I want MainTable.thirdParty to be nullable.
Whenever Hibernate creates the schema, it creates a FK relationship from MainTable.CUSTOMER_ID on ThirdParty.CUSTOMER_ID meaning I get a ref integrity problem when trying to persist MainTable with null thirdParty.
I just really want to be able to store an instance of MainTable which may be with or without ThirdParty data.
I don't want Hibernate to create the MainTable with a FK relation on ThirdParty.customerId

Related

hibernate-envers onetomany join class not audited, but join columns need to be audited

I am trying to save the join columns values in the audit tables while not auditing the join table.
Here are my entity classes.
Audited Class INFO:
#Entity
#Audited
#Table(name = "INFO")
public class Info extends AbstractEntity {
#Id
#Column(name = "ID")
private String id;
/*More Columns */
#ManyToOne
#JoinColumns({ #JoinColumn(name = "FIRST_NAME", referencedColumnName = "FIRST_NAME"),
#JoinColumn(name = "LAST_NAME", referencedColumnName = "LAST_NAME") })
#NotAudited
private Details details;
}
Not Audited Class: Details:
#Entity
#Table(name = "DETAILS")
public class Details extends AbstractEntity {
#EmbeddedId
private DetailsPK detailsPK;
/*More Columns */
}
Primary Key/ Join fields are defined here:
#Embeddable
public class DetailsPK implements Serializable {
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
}
Here, the Details information is static... It does not change... Hence, need ot be audited.... But, when auditing INFO changes, I want the INFO_AUD table to capture both the first name and last name as well... Is there a way to specify this?
Thank you,
Joe.
Since the fields you want are part of the primary key, you can very easily do this by specifying a special audit annotation attribute:
#ManyToOne
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
private Details details;
In short, this instructs Envers that the associated entity Details is not an audited entity; however the audit schema should take a snapshot of the associated entity's primary key columns.
So you should expect Info_AUD to have two columns that represent the name values called details_FIRST_NAME and details_LAST_NAME.
You won't need to do anything else special with your data model as Envers will make sure that as you change the Details object associated to Info, that the name columns are updated in the historical row snapshot accordingly.

Hibernate mapping join on one column of constants table

I have 2 tables, the first one is quite variable, the second one contains only constants:
USER.ID USER.NAME USER.USER_TYPE (FK on USER_TYPE.ID)
INT VARCHAR(64) INT(1)
----------------------------------
1 Alex 3
2 Jane 1
3 Carl 3
USER_TYPE.ID USER_TYPE.VALUE
INT(1) VARCHAR(64)
------------------------------
1 PENDING
2 REGISTERED
3 BANNED
4 ACTIVE
The foreign key USER.USER_TYPE is required and refering to a primary key USER_TYPE.ID in table USER_TYPE (one-to-one relation). Here is my mapping in Hibernate.
User.java
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE")
private UserType userType;
}
UserType.java
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "VALUE")
private String value;
}
My goal is to keep the enumerated values in the database. How to map UserType's value instead of id to User and validate it? I want to pass the constant VALUE to the String instead of its ID.
private String userType;
The expected result of the first user would be:
User[id=1, name=Alex, userType=Banned]
User[id=2, name=Jane, userType=Pending]
User[id=3, name=Carl, userType=Banned]
My attempt was to use this annotation on definition of table twice with both colums switched
#SecondaryTable(name="USER_TYPE",
pkJoinColumns={#PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_TYPE")}
)
and get the VALUE with
#Column(table="USER_TYPE", name="VALUE")
private String UserType;
however it leads to the error
Unable to find column with logical name: USER_TYPE in org.hibernate.mapping.Table(USER) and its related supertables and secondary tables
First you need to change the relation from #OneToOne to #ManyToOne as UserType can be used by one or many User and User can have one and one UserType.
Secondly use referencedColumnName which references :
The name of the column referenced by this foreign key column.
So User entity will be:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE", referencedColumnName = "VALUE")
private UserType userType;
}
In UserType you should apply a unique constraint using #NaturalId to value field + do not provide its setter, to prevent duplicate values as It may lead to inconsistency:
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#NaturalId
#Column(name = "VALUE")
private String value;
}
Hope it solves the issue!
Enumerations could be simpler:
enum UserType {
PENDING,
REGISTERED,
BANNED,
ACTIVE
}
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#javax.persistence.Enumerated
private UserType userType;
}
If you really need separated table and #OneToOne relation, you can use #Formula from Hibernate:
#Formula("(select ut.value from user_type ut where ut.ID = USER_TYPE)")
private String userType;
For this really special requirement you could use SecondaryTable annotation.
That is, you don't need UserType entity, but declare attribute userType as String in User entity with column mapping to the secondary table "USER_TYPE".
First of all, I suggest you use ManyToOne relation. and Not CascadeType.ALL if you are not planning update or delete on USER_TYPE table.
If you do not need adding new UserTypes frequently use enum for it. It will just work as you want.
Second solution: As long as fetch = FetchType.EAGER you can add A transient field and return value of UserType in getter.

Strange behaviour of spring data jpa

I'm new to JPA and I have a case where in my opinion JoinColumn behaves different and I want to know why.
UserEntites should join authorites.
Organziations should join OrganizationSettings.
I have two different approaches and both work.
Case 1
UserEntity :
#Entity
#Table(name = "users")
#Inheritance(strategy = InheritanceType.JOINED)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "userId")
private List<UserAuthority> authorities;
}
UserAuthoritiesEntity
#Entity(name = "authorities")
#Table(name = "authorities")
public class UserAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String authority;
}
Here in my opinion the JoinColumn name references to UserAuthority.userId - and it works as expected.
Case 2
See my two other classes:
OrganizationEntity:
#Entity
#Table(name="organization")
public class OrganizationEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#NotNull
private String url;
#NotNull
private String name;
#OneToOne (cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name="id",updatable = false)
private OrganizationSettingsEntity settings;
}
OrganizationSettings:
#Entity
#Table(name = "organization_settings")
public class OrganizationSettingsEntity {
#Id
private Long organizationId;
}
As you can see here -> Organization is Joining OrganizationSettings with the name id - which works. But in OrganizationSettings there is no id - just organizationId. This works - but makes me wonder.
Why does the second one also work? Shouldn't it be #JoinColumn(name="organizationId") ?
Spring is nothing to do with it. JPA is a standard API.
1-N case : you will create a FK column in the authorities table with name userId (linking back to the users table). You seem to also want to REUSE that same column for this userId field in the element ... this will cause you problems sooner or later since reusing columns without marking the userId field as insertable=false, updatable=false will mean that both may try to update it. Either get rid of the userId field in the element, or convert the field to be of type UserEntity (and have it as a bidirectional relation, using mappedBy on the 1-N owner field), or mark the userId field with those attributes mentioned earlier.
1-1 case : you will create a FK column in the organization table with name id (linking across to the organization_settings table). Sadly this is the same column as the PK of that table is going to use, so again you are reusing the column for 2 distinct purposes, and hell will result. Change the column of the relation FK to something distinct - the FK is in the organization table, not the other side.

One-To-Many Hibernate Entity Mapping : ids for this class must be manually assigned before calling save()

I am a new bee in Hibernate and I am using PostgreSQL 9.3, JDK 1.7, Hibernate 4.0.2
I am trying to save a Customer who Has-a relationship with Address i.e., One-To-Many Relation.
While saving the Customer i am getting the Exception:
javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.cust.entities.Address
Customer Entity:
#Entity
#Table(name="customer")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "CustomerIdSeq", sequenceName = "c_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CustomerIdSeq")
#Column (name="c_id")
private Long cId;
#Column(name="cname")
private String cname;
//bi-directional many-to-one association to Address
#OneToMany(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name="c_id")
private List<Address> address;
//getters and setters
}
Address Entity:
#Entity
#Table(name="address")
public class Address {
#Id
#Column(name="c_id")
private Long cId;
#ManyToOne
#PrimaryKeyJoinColumn(name="c_id", referencedColumnName="c_id")
private Customer customer;
#Column(name="street")
private String street;
#Column (name="city")
private String city;
//getters and setters
public void setCustomer(Customer customer) {
this.customer= customer;
this.cId= customer.getCId();
}
}
I had tried some thing which is similar to Java Persistence/Identity & Sequencing
I suspect that the Address record doesn't have an ID when calling save().
You're missing the #GeneratedValue tag in that class, and if not specified, it defaults to "assigned" value.
If you're not assigning a value to Address.cId before calling save(), you'll see this problem. Post all relevant code if this isn't the cause of the issue.
EDIT: Looking at your table structure, Address should really have it's own ID in the schema design, and have a foreign key(FK) reference to Customer.ID.
Form the comments to the previous answer and from your existing table structure, you may want to consider mapping Address as an Embeddable rather than as an Entity:
This is similar to a OneToMany, except the target object is an
Embeddable instead of an Entity. This allows collections of simple
objects to be easily defined, without requiring the simple objects to
define an Id or ManyToOne inverse mapping. ElementCollection can also
override the mappings, or table for their collection, so you can have
multiple entities reference the same Embeddable class, but have each
store their dependent objects in a separate table.
http://en.wikibooks.org/wiki/Java_Persistence/ElementCollection
The mappings would then be as follows. Address will have no persistent identity of its own and can only exist as part of an Entity - currently customer but no reason you cannot use it with other entities requiring an address.
Customer:
#Entity
#Table(name="customer")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "CustomerIdSeq", sequenceName = "c_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CustomerIdSeq")
#Column (name="c_id")
private Long cId;
#Column(name="cname")
private String cname;
#ElementCollection
#CollectionTable(name = "customer_address", joinColumns = #JoinColumn(name = "c_id")
private List<Address> addresses;
}
Address:
#Embeddable
public class Address {
#Column(name="street")
private String street;
#Column (name="city")
private String city;
}

JPA fetch from multiple tables to single POJO with named query

I have been trying to fetch data from 2 different tables to a single entity in JPA but with no result.
The entity that keeps data from two different tables is as below :
#Data #Entity #JsonSnakeCase
public class WareHouse {
#Id
#GeneratedValue
private long id;
#Column(unique = true)
private String fcId;
#Enumerated(EnumType.STRING)
private FCStatus status;
#OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, mappedBy = "fcId")
private List<WorkingHours> workingHours;
#Enumerated(EnumType.STRING)
private IntegrationType integrationType;
}
The other entity WorkingHours is :
#Data
#Entity
#JsonSnakeCase
public class WorkingHours {
#Id
#GeneratedValue
private long id;
private String fcId;
private LocalDate date;
private DateTime start;
private DateTime end;
}
The tables WareHouse and WorkingHours have one-to-many relationship and fcId is the column that joins them.
In my use case, I have to fetch WareHouse details and its WorkingHours in a single entity WareHouse as defined above. How do I achieve this ?
The named query (below) only fetches the WareHouse data and WorkingHours is coming empty. Is the data model wrong ? Or is the query wrong ? (I thought JPA would take care of automatically fetching from the related table when given the annotations OneToMany and FetchType etc.)
<named-query name="searchByFcId">
<query>
<![CDATA[
select f from WareHouse f where f.fcId = :fc_id
]]>
</query>
</named-query>
You can try the following mappings. The JPA 2.0 spec note (11.1.21) notes:
If the referencedColumnName element is missing, the foreign key is assumed to
refer to the primary key of the referenced table.
However it also goes on to note that:
Support for referenced columns that are not primary key columns of the
referenced table is optional. Applications that use such mappings
will not be portable.
So whether or not this works will depend on your provider.
Warehouse:
#Data
#Entity
#JsonSnakeCase
public class WareHouse {
#Id
#GeneratedValue
private long id;
#Column(unique = true)
private String fcId;
#Enumerated(EnumType.STRING)
private FCStatus status;
#OneToMany(mappedBy = "warehouse", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private List<WorkingHours> workingHours;
#Enumerated(EnumType.STRING)
private IntegrationType integrationType;
}
WorkingHours:
#Data
#Entity
#JsonSnakeCase
public class WorkingHours {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "fcId", referencedColumnName="fcid")
private Warehouse warehouse;
private LocalDate date;
private DateTime start;
private DateTime end;
}

Categories

Resources