#UniqueConstraint and #Column(unique = true) in hibernate annotation - java

What is difference between #UniqueConstraint and #Column(unique = true)?
For example:
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {#UniqueConstraint(columnNames = {"mask", "group"})}
)
And
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private ProductSerialMask mask;
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private Group group;

As said before, #Column(unique = true) is a shortcut to UniqueConstraint when it is only a single field.
From the example you gave, there is a huge difference between both.
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private ProductSerialMask mask;
#Column(unique = true)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private Group group;
This code implies that both mask and group have to be unique, but separately. That means that if, for example, you have a record with a mask.id = 1 and tries to insert another record with mask.id = 1, you'll get an error, because that column should have unique values. The same aplies for group.
On the other hand,
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {#UniqueConstraint(columnNames = {"mask", "group"})}
)
Implies that the values of mask + group combined should be unique. That means you can have, for example, a record with mask.id = 1 and group.id = 1, and if you try to insert another record with mask.id = 1 and group.id = 2, it'll be inserted successfully, whereas in the first case it wouldn't.
If you'd like to have both mask and group to be unique separately and to that at class level, you'd have to write the code as following:
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {
#UniqueConstraint(columnNames = "mask"),
#UniqueConstraint(columnNames = "group")
}
)
This has the same effect as the first code block.

From the Java EE documentation:
public abstract boolean unique
(Optional) Whether the property is a unique key. This is a shortcut for the
UniqueConstraint annotation at the table level and is useful for when the unique key
constraint is only a single field. This constraint applies in addition to any constraint
entailed by primary key mapping and to constraints specified at the table level.
See doc

In addition to Boaz's answer ....
#UniqueConstraint allows you to name the constraint, while #Column(unique = true) generates a random name (e.g. UK_3u5h7y36qqa13y3mauc5xxayq).
Sometimes it can be helpful to know what table a constraint is associated with. E.g.:
#Table(
name = "product_serial_group_mask",
uniqueConstraints = {
#UniqueConstraint(
columnNames = {"mask", "group"},
name="uk_product_serial_group_mask"
)
}
)

In addition to #Boaz's and #vegemite4me's answers....
By implementing ImplicitNamingStrategy you may create rules for automatically naming the constraints. Note you add your naming strategy to the metadataBuilder during Hibernate's initialization:
metadataBuilder.applyImplicitNamingStrategy(new MyImplicitNamingStrategy());
It works for #UniqueConstraint, but not for #Column(unique = true), which always generates a random name (e.g. UK_3u5h7y36qqa13y3mauc5xxayq).
There is a bug report to solve this issue, so if you can, please vote there to have this implemented. Here:
https://hibernate.atlassian.net/browse/HHH-11586
Thanks.

Related

Problem with ManyToMany relations in spring data. It changed my table

I had a db with tables SPEC and PARTS.Also I had a table for MANY TO MANY relations. In my project I used spring jdbs template and all works good. Then I decide to change jdbc on SPring data jpa.
My Entities:
#Entity
#Table(name = "PARTS")
public class PartsJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_EXPORT", unique = false, nullable = false, updatable = true)
private ExportJpa exportJpa;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "ID_TYPE", unique = false, nullable = false, updatable = true)
private TypesJpa typesJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<SpecJpa> specJpa;
////////
}
And Spec:
#Entity
#Table(name = "SPEC")
public class SpecJpa {
#Id
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "Creator_ID", unique = false, nullable = false, updatable = true)
private UsersJpa usersJpa;
#Column(name = "DESCRIPTION")
private String description;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
private Set<PartsJpa> partsJpa;
////////////////
}
I don't show getters and setters.
It works, but when I start a programm, something in my table was changed and now I can't add in table spec_parts values like(1,3)(1,2).
Mistake:
FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)" Referential integrity constraint violation: "FK_123: PUBLIC.SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)"; SQL statement: INSERT INTO "PUBLIC"."SPEC_PARTS"("ID_SPEC","ID_PARTS")VALUES(?,?)
Maybe I have mistake with creating relations between spec and parts? What problem it can be?
data in spec
ID NAME CREATOR_ID DESCRIPTION CHANGER_ID
1 pc 1 description 1
2 pc2 2 description2 2
data in parts
ID ▼ NAME ID_EXPORT ID_TYPE DESCRIPTION
1 intel core i5 1 1 d1
2 intel core i7 1 1 d2
3 ddr3 2 2 d3
4 ddr4 2 2 d4
5 asus 3 3 d5
data in spec_parts now:
ID_SPEC ID_PARTS
1 1
2 2
so I can't add 1,3 or 2,4
I find a problem, spring date change something and now in table SPEC_PARTS ID_SPEC mapping on PARTS.ID. Why?
As you are using ManyToMany relation, there is a mapping table created named SPEC_PARTS which have referenced columns ID_SPEC and ID_PARTS.These columns value come from SPEC.ID and PARTS.ID. So you can't insert in SPEC_PARTS without creating referenced value because you are trying to do foreign key constraint violation.
Suppose if there is a row in SPEC with id value 1 and there is a row in PARTS with id value 2. Then you can insert in SPEC_PARTS with value like (1,2).
So, first, add data in SPEC and PARTS then map them in SPEC_PARTS.
And you can remove #JoinTable from one side, you don't need to define it both side.
Update:
Problem is SpecJpa class relation. Here you are using SPEC_PARTS.ID_SPEC as foriegn key for PARTS.ID and SPEC_PARTS.ID_PARTS as foriegn key for SPEC.ID which is fully reversed what you do in PartsJpa class.
#JoinTable(name="SPEC_PARTS",
joinColumns = #JoinColumn(name="ID_SPEC", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="ID_PARTS", referencedColumnName="id")
)
That's why this error say
SPEC_PARTS FOREIGN KEY(ID_PARTS) REFERENCES PUBLIC.SPEC(ID) (3)";
There is no SPEC.ID value 3 exist in the database.
Solution:
Remove #JoinTable from SpecJpa class as you don't need to specify both side.
And remove the wrong relation of the foreign key from database also.

Hibernate join using non-primary key column

I have two tables:
users:
user_id (primary)
ip (unique)
etc ..
services_to_ip
id (primary)
service_id
ip
etc ..
In class User:
#OneToMany()
#JoinColumn(name = "ip", insertable = false, nullable = false, updatable = false)
public List<QPlanService> getPlanServices() {
return planServices;
}
Using MySQL query log we get something like that:
SELECT *
FROM services_users planservic0_
LEFT OUTER JOIN services qservice1_
ON planservic0_.service_id = qservice1_.id
WHERE planservic0_.ip = 777
In WHERE condition the 'user_id' field used (the default field is primary key - users.id) (user_id=777).
How can I specify that I need to take the value of the 'ip' field from User entity, not 'user_id'?
I will be grateful for any help!
JoinColumn will only specify the name of the column holding the foreign key, so changing the name of joincolumn absolutely will not help.
Hibernate will be using the primary key by default for joining, if you want to override this you can simply use referencedColumnName in your relation, but the referenced column should be unique
As Amer Qarabsa mentioned above:
#OneToMany()
#JoinColumn(name = "ip", insertable = false, nullable = false, updatable = false, referencedColumnName="ipcolumnName")
public List<QPlanService> getPlanServices() {
return planServices;
}

Hibernate sql annotation

I am using Hibernate to interface with SQL Server 2016/Azure SQL Server currently, and have been having a great time with it so far. In my database, I have implemented system versioned temporal tables. I want to map (preferably lazily) two more variables by annotation only to my Hibernate entity that represent the original ValidFrom and UpdatedBy fields from the temporal history of the appropriate table.
For example, I have a class and table for Accounts. The Account [minus nonrelated columns, constraints, etc] table is as follows:
CREATE TABLE [dbo].[Account] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[UpdatedBy] INT NOT NULL,
[ValidFrom] DATETIME2 (7) GENERATED ALWAYS AS ROW START DEFAULT (sysutcdatetime()) NOT NULL,
[ValidTo] DATETIME2 (7) GENERATED ALWAYS AS ROW END DEFAULT (CONVERT([datetime2],'9999-12-31 23:59:59.9999999')) NOT NULL,
CONSTRAINT [FK_Account.UpdatedById_Account.Id] FOREIGN KEY ([UpdatedBy]) REFERENCES [dbo].[Account] ([Id]),
PRIMARY KEY CLUSTERED ([Id] ASC),
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE=[dbo].[AccountHistory], DATA_CONSISTENCY_CHECK=ON));
The SQL statement to get the data that I want looks like this (I imagine that I would select only UpdatedBy or ValidFrom per annotation, but they are together now to be concise):
SELECT UpdatedBy, ValidFrom FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ValidFrom IN
(
SELECT MIN(ValidFrom) OVER (Partition BY Id) AS ValidFrom
FROM dbo.Account
FOR SYSTEM_TIME ALL
WHERE ID = $(passedInIdOfThisEntity)
)
Finally, my Hibernate entity/pojo looks something like this (again, redacting irrelevant variables):
#Entity
#Table(name = "Account")
public class Account implements Serializable {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "UpdatedBy")
private Account updatedBy;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", nullable = false, length = 27, insertable = false, updatable = false)
private Date validFrom;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidTo", nullable = false, length = 27, insertable = false, updatable = false)
private Date validTo;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "updatedBy")
private Set<Account> accountsUpdated;
// This is a stub of what I'm hoping you can help me add
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "ValidFrom", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Date createdOn;
#Column(name = "UpdatedBy", fetch = FetchType.LAZY, insertable = false, updatable = false, somesqlselect = SQL_STATEMENT_FROM_ABOVE)
private Account createdBy
// ... getters and setters below
}
I have been using Hibernate to a great extent, but have had trouble finding information on this, though I have found and used examples of implementing native queries for retrieving entities instead of using criteria queries. If you can help me solve this riddle to allow me to continue using criteria queries to retrieve data and populate these fields through annotation on demand, I would greatly appreciate it.
The temporal table constructs that you've described isn't something that I am aware that JPA or even Hibernate support natively. These are likely new features of the ANSI SQL standard which haven't made their way into proper support.
That said, that doesn't mean you cannot use frameworks like Hibernate to accomplish the task. As indicated in the comments, you can specify a named query and execute that in order to get the attributes you desire.
From a JPA 2.1 perspective, you use #SqlResultSetMapping and #ConstructorResult.
#SqlResultSetMapping(
name = "Account.getWithTemporalAttributes",
classes = {
#ConstructorResult(
targetClass = com.company.domain.AccountTemporalDetails.class,
columns = {
#ColumnResult(name = "col1"),
#ColumnResult(name = "col2")
})
})
To use this, you would do the following:
Query query = entityManager.createNativeQuery(
"SELECT a.col1 as col1, a.col2 as col2 FROM Account a",
"Account.getWithTemporalAttributes");
List<AccountTemporalDetails> results = query.getResultList();
That should allow you to use Native SQL queries, mapping them to a POJO which you can easily then use within your application without having to write boilerplate. The #ConstructorResult annotation is meant to mimic the JPQL SELECT NEW syntax. So you would just need to make sure that AccountTemporalDetails had a constructor that takes those arguments with the right types.

hibernate cannot find sequence

I need a simple sequence which would give me incremented integers that I later on use as part of a String.
I made this sequence using postgresql command line:
CREATE SEQUENCE my_seq
INCREMENT BY 1
The sequence exists as I can query it from postgresql command line, But I'm trying to get the values using hibernate:
Query query = session.createSQLQuery("select nextval(:sequence);");
query.setParameter("sequence", "my_seq");
Long nextVal=((BigInteger)query.uniqueResult()).longValue();
And I am getting this exception:
ERROR: relation "my_seq" does not exist
The sequence values do NOT represent the attribute of any entity. I only need them to store the number of logins, but I do not store the logins as entities.
EDIT: I got it working by adding the scheme name to the query:
String query1 = "select nextval(myscheme.my_seq)";
Query query = session.createSQLQuery(query1);
I can't figure out why it needed the scheme though, as myscheme was already default and all other queries worked fine without specifying the scheme. If anyone can shed some light I shall accept its answer.
You don't need to map a sequence to an entity.
Just leave the mapping as follow for the ID column of your entity, and the id of the tntity will be generated from the sequence my_seq. You don't need to call nextval(my_seq) or anything else.
#Entity("myEntity")
public class MyEntity {
#Id
#Column(name = "id", unique = true, nullable = false, insertable=false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq")
#SequenceGenerator(name = "my_seq", sequenceName = "my_seq", allocationSize = 1)
private long id;
}
Below is the logic which will use a sequence called "my_seq" from the database and map it to the column "column_name" table "table_name"
Soon after you import the classes the sequence generator should be placed before you begin the class
#Entity
#org.hibernate.annotations.Entity(dynamicInsert = true, dynamicUpdate = true)
#Table(name = "table_name")
#SequenceGenerator(name = "my_seq", sequenceName = "my_seq")
public class ClassName implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "my_seq")
#Column(name = "column_name")
private Long columnName;
}
Let me know if this is useful.
In order to avoid hardcoding schema to your query, you can set hibernate.default_schema property in your persistence.xml as follows.
<property name="hibernate.default_schema" value="myscheme"/>
This could be also used in spring configuration file as is stated here.

JPA composite primary key with null value

I have a table containing customer data in an oracle database. Here is a simplified definition:
CUSTOMER (CUSTOMER_ID NUMBER NOT NULL,
SOURCE_SYSTEM VARCHAR2(30),
FULL_NAME VARCHAR2(360),
PHONE_NUMBER VARCHAR2(240)
)
The primary key for this table is (CUSTOMER_ID, SOURCE_SYSTEM).
The table has numerous rows for which SOURCE_SYSTEM is null. At the database level, there is no issue, however when I try to access any of these rows via JPA Entity, it causes a number of issues:
1: Using em.find() to fetch a row with a null SOURCE_SYSTEM always results in a null being returned.
2: Using em.merge() to upsert a row with a null SOURCE_SYSTEM succeeds if the record does not exist in the table, but fails on subsequent updates because the merge ALWAYS results in an insert being run.
3: Using em.createQuery() to explicitly query for a row with a null causes the following exception:
Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.3.1.v20111018-r10243):
org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [ArrayRecord(
CUSTOMER.CUSTOMER_ID => 1
CUSTOMER.FULL_NAME => GUY PERSON
CUSTOMER.PHONE_NUMBER => 555-555-1234
CUSTOMER.SOURCE_SYSTEM => null)] during the execution of the query was detected to be null.
Primary keys must not contain null.
Query: ReadAllQuery(referenceClass=Customer sql="SELECT CUSTOMER_ID, FULL_NAME, PHONE_NUMBER, SOURCE_SYSTEM FROM CUSTOMER WHERE ((CUSTOMER_ID = ?) AND (SOURCE_SYSTEM IS NULL))")
Unfortunately, "Primary keys must not contain null" seems pretty final. I was unable to find too much information on workarounds for this error, which makes it seem like there is no solution.
THE QUESTION: I would like to know if anyone has any Java code-based solution that don't involve making changes to the database. My current workaround is to use ROW_ID as the #Id of the table, but this means I can no longer use em.merge() or em.find().
Here are my Java classes:
Customer.java:
#Entity
#Table(name = "CUSTOMER")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private Customer_Id key;
#Column(name = "CUSTOMER_ID", nullable = false, insertable = false, updatable = false)
private Long customerId;
#Column(name = "SOURCE_SYSTEM", length = 30, insertable = false, updatable = false)
private String sourceSystem;
#Column(name = "FULL_NAME", length = 360)
private String fullName;
#Column(name = "PHONE_NUMBER", length = 240)
private String phoneNumber;
//Setters, Getters, etc
...
}
Customer_Id.java
#Embeddable
public class Customer_Id implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "CUSTOMER_ID", nullable = false)
private Long customerId;
#Column(name = "SOURCE_SYSTEM", length = 30)
private String sourceSystem;
//Setters, Getters, etc
...
}
Primary keys cannot contain null (in JPA or in databases). Use a different value such as "" or " ".
Is the customer_id unique? if so then just remove the sourceSystem from the Id.
Otherwise, you could try logging a bug to have support for null ids added.

Categories