How to let hibernate create a complex unique constraint? - java

I'm using hibernate to autogenerate my tables.
Now I want to add a rather complex unique constraint as follows:
UNIQUE KEY person_unique ((coalesce(firstname, 'null')), (coalesce(lastname, 'null')), (coalesce(dob, 'null')));
#Entity
public class Person {
#Id long id;
#Column(nullable=true)
String firstname, lastname;
#Column(nullable=true)
LocalDate dob;
}
I want to use the constraint to prevent inserting duplicate entries also respecting null value fields.
But how can I tell hibernate to create those unique key?
Because a #Table annotation does not accept a native sql statement. The following is not sufficient:
#Table(name = "person",
uniqueConstraints = {#UniqueConstraint(name = "unique_person",
columnNames = {"firstname", "lastname", "dob"}) }
)
Question: how can I get the coalesce() there inside?

Related

JPA (Hibernate) OneToOne Impedance Mismatch

I am studying JPA and Hibernate to build a Spring Boot webapp, and there's something that bugs me.
It is related to impedance mismatch in One To One relationships.
Let's say I have two domain entities, A and B, that have a one to one relationship.
This is what I would like to have:
in the Java Classes, I would like to have A hold a reference to B;
in the Database, I would like to have the table for "b" objects have a column with the foreign key to "a" keys.
Is there a way to do this with JPA and Hibernate in Spring Boot?
I report here the problem with real-world classes and code.
In my domain I have basically people and signatures.
Therefore, in my Java Code, I have Person #Entity and a Signature #Entity.
In Java, it makes sense to have the Person object own a Signature object.
So, here is the Person class:
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="first_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String firstName;
#Column(name="last_name")
#NotNull
#NotBlank
#Size(min = 3, max = 100)
private String lastName;
// ??? which annotations?
private Signature signature;
// I omit constructors, getters and setters for brevity
And this is the Signature class:
#Entity
#Table(name = "signatures")
public class Signature {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name="name")
#NotNull
#NotBlank
private String name;
#Column(name="type")
#NotNull
private String type;
#Column(name="image")
#NotNull
#NotEmpty
#Lob
#Type(type="org.hibernate.type.MaterializedBlobType")
private byte[] image;
// I omit constructors, getters and setters for brevity
As you can see, Ids should be generated automatically, and I would like my Person class to have a reference to its Signature, and not vice-versa.
On the contrary, this is the DB schema I'd like to use:
CREATE SCHEMA signatures;
CREATE TABLE signatures.people (
id BIGSERIAL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE signatures.signatures (
id BIGSERIAL,
type VARCHAR[16] NOT NULL,
name VARCHAR[100] NOT NULL,
image BYTEA NOT NULL,
person BIGINT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_signature_people FOREIGN KEY (person) REFERENCES signatures.people (id) ON DELETE CASCADE ON UPDATE CASCADE
);
As you can see here, I would like the Signatures table to have a foreign key to the People table, and not vice-versa.
Is this possible?
The #OneToOne mapping is a bit of an odd-ball. When the relationship is bi-directional, you can decide the owning side, but in a unidirectional relationship the declaring entity will always be the one with the foreign key.
One option is to make the relationship bi-directional, but hide the other direction in code.
The other way is to use a #OneToMany mapping, which will create the foreign key in the "many" table. This is also consistent with the database schema, as multiple child table rows could then link to the same parent row at least theoretically, especially if there's not a constraint to make sure they're unique.

How to trigger foreign key relation without adding a reference #Entity in hibernate?

The following relationship creates a foreign key mapping
#Entity
public class Department {
#Id
private String name;
//some more fields
}
#Entity
public class Employee {
#Id
private long id;
private String name;
private String designation;
#ManyToOne
#JoinColumn(name = "fk_department_id", foreignKey = #ForeignKey(name="fk_department"))
private Department department;
}
generates:
...CONSTRAINT fk_department FOREIGN KEY (fk_department_id) REFERENCES department (name)
Question: how can I trigger this constraint creation in hibernate without having to create the Department entity?
Eg just adding the foreign key #Id field without an explicit entity reference. But still trigger the fk on initial creation. The following is of course invalid:
#ManyToOne
#JoinColumn(name = "fk_department_id", foreignKey = #ForeignKey(name="fk_department"))
private String department;
You get the intention. Is that possible?
(sidenote: I'm not interested in creating that foreign key link by startup ddl/sql statements).
You'll have to drop #ManyToOne at least, since that's for entities.
The following should work by overriding the column definition to include the foreign key while creating it
#Column(name = "department_id", columnDefinition = "VARCHAR(255), foreign key (department_id) REFERENCES department(name)")
private String department;
Now there's only a column and a constraint defined, but no relation (as far as Hibernate knows) defined between entities.
Essentially copied from Hibernate and JPA: how to make a foreign key constraint on a String but that was darn hard to find, so I'm not just going to close this as a duplicate! ;)

Conditional unique constraint using annotations

I have two related entities with two different types(GENERAL and CUSTOM) and I save it in the same table. Entity with type GENERAL should have unique values of field name and CUSTOM can have duplicates for different users and not duplicate GENERAL name.
I'm looking for a way to create conditional unique constraint in order to check next cases:
if entity has type GENERAL, name should be unique
if entity has type CUSTOM, name can be duplicated in the table but can't duplicate GENERAL items and should be unique for specific user(by user id)
#Entity
#Table(name = "Purpose", uniqueConstraints = #UniqueConstraint(columnNames = {"purposeId"}))
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class GeneralPurpose {
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="purpose_seq_gen")
#SequenceGenerator(name="purpose_seq_gen", sequenceName="PURPOSE_SEQ")
#Column(name = "purposeId", nullable = false)
private long purposeId;
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
private PurposeType type;
#Column(name = "name", nullable = false)
private String name;
#Entity
#Table(name = "Purpose")
public class CustomPurpose extends GeneralPurpose {
#ManyToOne()
#JoinColumn(name="id")
#JsonIgnore
private User user;
public enum PurposeType {
GENERAL, CUSTOM
}
You could do this by adding another column to the Purpose table. This column is to store a constant value for GENERAL records, and the user id for CUSTOM records. For GENERAL records, the value could be 0 (if the user id is numeric) or "GENERAL" (if the user id is a string). It could be named 'userOfRecord' or 'recordDiscriminator', something like that.
Then you can add a unique constraint on [ type, name, userOfRecord ].

HIbernate one-to-one annotation isn't generating foreign key GerericGenerator in dependent table

I am trying to create OneToOne relation between a Person and Auth table. The problem is when the DB table "Auth" is generated, I'm not seeing the foreign key in the AUTH table that should reference Person. The object is to have the Auth table use the same Primary Key of the Person Table.
#MappedSuperclass
public abstract class DomainBase {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Version
#Column(name="OPLOCK")
private Integer version;
}
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
---------------------------------
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Person person;
}
My Database scripts after hibernate DB generation.
CREATE TABLE auth
(
person_id integer NOT NULL,
activate boolean,
activationid character varying(255),
last_login_attempt_date timestamp without time zone,
last_login_attempt_timezone character varying(255),
last_login_date timestamp without time zone,
last_login_timezone character varying(255),
nonlocked boolean,
num_login_attempts integer,
CONSTRAINT auth_pkey PRIMARY KEY (person_id),
CONSTRAINT uk_d68auh3xsosyrjw3vmwseawvt UNIQUE (activationid)
)
WITH (
OIDS=FALSE
);
ALTER TABLE auth
OWNER TO postgres;
It seems that the problem is you declare twice the #OneToOne annotation between "person" table and "auth" table, without specify the relation between them. Take a look at the hibernate documentation, at the point 2.2.5.1, there is some examples about using one-to-one association.
For me, the best way is to set up the association in one table, the one that declare the foreing key column, and to use the mappedBy parameter in the other object. In your code, this will be :
#Entity
#Table(name = "person")
public class Person extends DomainBase {
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="auth_id")
private Auth auth;
}
#Entity
public class Auth {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="person")
})
#Column(name="person_id")
private int personId;
#OneToOne(mappedBy = "auth")
private Person person;
....
}
This is the second example in the hibernate documentation, introduce just after the sentence "In the following example, the associated entities are linked through an explicit foreign key column". I tested this code, and the "auth_id" column appeared.

Hibernate mapping with two tables

I am trying to learn Hibernate and I could create some simple CRUD operation using a Single Class and Single Table. I am just reading the Hibernate Doc and some online tutorial.
But I have a problem on how to define this relationship with two tables involved. I basically have an Employee table with this structure.
CREATE TABLE EMPLOYEE
(
EMP_ID VARCHAR(10) NOT NULL,
EMP_FIRST_NAME VARCHAR(30) NOT NULL,
EMP_LAST_NAME VARCHAR(30) NOT NULL,
STATUS_ID INT NOT NULL,
PRIMARY KEY (EMP_ID)
);
The STATUS_ID field references another table. STATUS_DESC can either be 'PERMANENT', 'CONTRACTUAL', 'ON-DEMAND'
CREATE TABLE EMP_STATUS
(
STATUS_ID VARCHAR(10) NOT NULL,
STATUS_DESC VARCHAR(100) ,
PRIMARY KEY (STATUS_ID)
);
I am thinking of having an Entity class like this. Now my goal is to return list of Employee object with status, but I don't know how to go about on doing this.
#Entity
public class Employee{
//other private instance
private EmployeeStatus empStatus;
//getters and setters.
}
public class EmployeeStatus{
private int statusID;
private String statusDesc;
//getters and setters
}
You want to know how to map it? ManyToOne?
Employee.java
#Entity
public class Employee{
//other private instance
#JoinColumn(name = "empStatus", referencedColumnName = "yourColName")
#ManyToOne(optional = false)
private EmployeeStatus empStatus;
//getters and setters.
}
Dont forget to change "referencedColumnName" value...
EmployeeStatus.java
#Entity
public class EmployeeStatus{
#Id //this is your pk?
private int statusID;
private String statusDesc;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "empStatus", fetch = FetchType.LAZY) //or EAGER
private List<Employee> empList;
//getters and setters
}
To create a relationship between two tables you need to decide:
Is the relationship bi-directional? That is, do the statuses know the employees or not? If no then it is uni-directional. In that case you can add the annotation on the Employee class like this:
#ManyToOne
#JoinColumn(name = "status")
private EmployeeStatus empStatus;
And there is a few other options that you may add.
You can do what you are doing, but I would suggest, if the status can only be one of three values, create an Enum with the three values. No need for a separate table.
The downside for this is you need to create a hibernate custom type (the code is on the wiki) to support persisting enums.
A simpler answer is to not use a secondary table, and just save the status as a String on the domain object. You can put business logic on your model to ensure the String is in the list of acceptable values.
If you really want to use a relationship between two entities, then check out the hibernate docs on many-to-one relationships.
You can use HQL to query the entities. Like so
Query q = s.createQuery("from Employee as e where e.empStatus = :status");
q.setParameter("status", status);
List emps= q.list();

Categories

Resources