How to set the name of a class level jpa check constraint - java

I would like to define a constraint in JPA on an entity to make sure that either one of two properties "text" or "title" is set to a non-null value.
For this example, suppose the following Question entity class:
#Entity
#Table
public class Question {
#Id
private Long id;
#Column(nullable = true)
private String text;
#Column(nullable = true)
private String title;
}
For this class, JPA will generate the following SQL statement (we need to use the oracle dialect):
create table question (
id number(19,0) not null,
text varchar2(50 char) null,
title varchar2(10,0) null,
primary key (id)
);
In order to check that either one of the properties is set, I could add a check constraint:
#Entity
#Table
#Check(constraints = "TEXT IS NOT NULL OR TITLE IS NOT NULL")
public class Question {
...
}
Now JPA will generate this:
create table question (
id number(19,0) not null,
text varchar2(50 char) null,
title varchar2(10,0) null,
primary key (id),
check (TEXT IS NOT NULL OR TITLE IS NOT NULL)
);
On the database side, this will generate a check constraint with a random name like "SYS_C00127157".
In order to assign a meaningful name (for example: check_title) to the constraint, I could use this DDL:
create table question (
id number(19,0) not null,
text varchar2(50 char) null,
title varchar2(10,0) null,
primary key (id),
constraint check_title check(TEXT IS NOT NULL OR TITLE IS NOT NULL)
);
What I am looking for is something like this:
#Entity
#Table
#Check(name = "check_title" constraints = "TEXT IS NOT NULL OR TITLE IS NOT NULL")
public class Question {
...
}
Unfortunately, this is not possible. The #Check annotation in Java does not offer such a name property for the constraint.
Is there any other way to set a name so that the generated DDL will match the expected result?

It's not possible, unfortunately. If you take a look at how the table generation script is built:
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/mapping/Table.java#L604
you'll see that the string, which you specify in #Check annotation is just wrapped like this:
buf.append( ", check (" )
.append( checkConstraint )
.append( ')' );
and the generation of a constraint name is given to the database.
In contrast, a few lines above, you can find that it's possible to influence the name of the unique constraints. Here you can find an example of it.

That's not possible.
In my opinion it's also not a good idea to generate the production database from JPA annotations.
The datamodel usually lives longer than the application and therefore it's good to have the DDL under control.
You should consider to use FlyWay or Liquibase for your database migrations.

Related

Why is a entity - value relationship implemented as a back reference in Spring Data JDBC

In Spring Data JDBC if an entity (e.g. Customer) has a value (e.g. Address) like in the example here the value has a back reference column (column customer in table address) to the entity in the db schema:
CREATE TABLE "customer" (
"id" BIGSERIAL NOT NULL,
"name" VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE "address" (
"customer" BIGINT,
"city" VARCHAR(255) NOT NULL
);
The problem with this is that if you use that Address value more than once in one entity or even in different entities you have to define an extra column for each usage. Only the primary id of the entity is stored in these columns and otherwise there is no way to distinguish from which entity it is. In my actual implementation I have five of these columns for the Address value:
"order_address" BIGINT, -- backreference for orderAddress to customer id
"service_address" BIGINT, -- backreference for serviceAddress to customer id
"delivery_address" BIGINT, -- backreference for deliveryAddress to customer id
"installation_address" BIGINT, -- backreference for installationAddress to provider_change id
"account_address" BIGINT, -- backreference for accountAddress to payment id
I understand how it works, but I don't understand the idea behind this back reference implementation. So can someone please shed some light on that issue? Thanks!
As to most good questions there are many sides to the answer.
The historical/symmetry answer
When it comes to references between entities Spring Data JDBC supports 1:1 (the one you ask about) and 1:N (lists, sets and maps).
For the latter anything but a back-reference is just weird/wrong.
And with using a back-reference for 1:1 becomes basically the same, simplifying the code, which is a good thing.
The DML process answer
With the back-reference, the process of inserting and deleting becomes much easier: Insert the aggregate root (customer in your example) first, then all the referenced entities. And it continues to work if those entities have further entities. Deletes work the other way round but are equally straight forward.
The dependency answer
Referenced entities in an aggregate can only exist as part of that aggregate. In that sense they depend on the aggregate root. Without that aggregate root there is no inner entity, while the aggregate root very often might just as well exist without the inner entity. It therefore makes sense, that the inner entity carries the reference.
The ID answer
With this design, the inner entity doesn't even need an id. It's identity is perfectly given by the identity of the aggregate root and in case of multiple one-to-one relationships to the same entity class, the back-reference column used.
Alternatives
All the reasons are more or less based on a single one-to-one relationship. I certainly agree that it looks a little weird for two such relationships to the same class and with 5 as in your example it becomes ridiculous. In such cases you might want to look in alternatives:
Use a map
Instead of modelling your Customer class like this:
class Customer {
#Id
Long id;
String name;
Address orderAddress
Address serviceAddress
Address deliveryAddress
Address installationAddress
Address accountAddress
}
Use a map like this
class Customer {
#Id
Long id;
String name;
Map<String,Address> addresses
}
Which would result in an address table like so
CREATE TABLE "address" (
"customer" BIGINT,
"customer_key" VARCHAR(20). NOT NULL,
"city" VARCHAR(255) NOT NULL
);
You may control the column names with a #MappedCollection annotation and you may add transient getter and setter for individual addresses if you want.
Make it a true value
You refer to Address as a value while I referred to it as an entity. If it should be considered a value I think you should map it as an embedded like so
class Customer {
#Id
Long id;
String name;
#Embedded(onEmpty = USE_NULL, prefix="order_")
Address orderAddress
#Embedded(onEmpty = USE_NULL, prefix="service_")
Address serviceAddress
#Embedded(onEmpty = USE_NULL, prefix="delivery_")
Address deliveryAddress
#Embedded(onEmpty = USE_NULL, prefix="installation_")
Address installationAddress
#Embedded(onEmpty = USE_NULL, prefix="account_")
Address accountAddress
}
This would make the address table superfluous since the data would be folded into the customer table:
CREATE TABLE "customer" (
"id" BIGSERIAL NOT NULL,
"name" VARCHAR(255) NOT NULL,
"order_city" VARCHAR(255) NOT NULL,
"service_city" VARCHAR(255) NOT NULL,
"deliver_city" VARCHAR(255) NOT NULL,
"installation_city" VARCHAR(255) NOT NULL,
"account_city" VARCHAR(255) NOT NULL,
PRIMARY KEY (id)
);
Or is it an aggregate?
But maybe you need addresses on their own, not as part of a customer.
If that is the case an address is its own aggregate.
And references between aggregates should be modelled as ids or AggregateReference. This is described in more detail in Spring Data JDBC, References, and Aggregates

Hibernate query ends up in SQL syntax error

I have created a UserPreferences bean. The primary key being id column. Here is the definition of the bean:
#Entity
public class UserPreferences {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#Column(unique = true)
#ColumnDefault("")
private String id;
private String email;
// Getters and Setters
}
Now I use Kotlin to insert data in this bean. Here is the code to do so:
val newUserPreferences = UserPreferences().apply { email = newUserRequest.email }
mUserPreferenceService.save(newUserPreferences)
Lets assume that newUserRequest.email is not null. The auto-dll setting in my application.yml is update. When this code is executed, I see that Hibernate generates following query for creating the table:
create table user_preferences (
id varchar(255) default not null
email varchar(255),
primary key (id)
) engine=MyISAM
At this point, the application fails to load with the following error:
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
You have an error in your SQL syntax; check the manual that
corresponds to your MariaDB server version for the right syntax to use
near 'not null,
Looking at the exception, it is very clear that the code is failing because instead of using an empty String as default value, hibernate is simply putting a blank space in the generated query. So here are my questions:
Should this be treated as a bug in Hibernate?
How to handle this situation where I would like to have an empty String "" as column default value?
I'm not a Hibernate user, but I found https://github.com/hibernate/hibernate-orm/blob/master/documentation/src/test/java/org/hibernate/userguide/schema/ColumnDefaultTest.java shows:
#ColumnDefault("'N/A'")
It appears that when you define a default value for a SQL string type, you must use SQL string delimiters (single quotes) inside the Java string. In your case, since you want an empty string, you'd use:
#ColumnDefault("''")
The other way to define a default is to set an attribute in the #Column annotation, as shown in another Stack Overflow answer: How to set default value in Hibernate
So this may not be a bug, but it's a usage of Hibernate that is counter-intuitive for some users, and perhaps not documented clearly.
P.S.: I would count it as a bug that Hibernate still generates CREATE TABLE statements with engine=MyISAM unless you specify the right hibernate.dialect. That storage engine is not recommended. MyISAM is slower than InnoDB in most cases, and fails to support any ACID properties. MySQL has been phasing out MyISAM, and I don't recommend using it for any current projects.

Mapping a view with a candidate key that may have nulls in JPA

I have a view on which the only possible candidate key may have a null value. Consider:
CREATE TABLE FOO (
FOO_ID NUMBER(10,0),
CONSTRAINT PK_FOO PRIMARY KEY (FOO_ID)
);
CREATE TABLE BAR (
BAR_ID NUMBER(10,0),
FOO_ID NUMBER(10,0),
CONSTRAINT PK_BAR PRIMARY KEY (BAR_ID),
CONSTRAINT FK_BAR_FOO FOREIGN KEY (FOO_ID) REFERENCES FOO(FOO_ID)
) ;
-- I'm interested in this view!
CREATE VIEW VW_FOO_BAR AS
SELECT FOO.FOO_ID, BAR.BAR_ID
FROM FOO
LEFT JOIN BAR ON FOO.FOO_ID = BAR.FOO_ID
Needless to say, this is only a MCVE. The actual DDL is much more complex.
Note that, in this view, BAR_ID may be null, since it's a LEFT JOIN. But FOO_ID may be repeated multiple times, so it can't be the key by itself. As such, the only possible candidate key is (FOO_ID, BAR_ID)
It's not possible to make any changes on the database or its views. Also, this view has some important information that is only accessible through it, so I have to use it.
Since it's a view, I will never be making insertions or updates on it. Only queries.
Question:
Is there a way to map this view in JPA, without triggering the infamous "More than one row with the given identifier" exception?
Discarded solutions:
This implementation may return multiple rows with the same identifier, causing the above mentioned exception:
#Entity
#Table(name = "VW_FOO_BAR")
class VwFooBar {
#Id
#Column(name="FOO_ID")
private Long idFoo;
#Column(name="BAR_ID")
private Long idBar
}
This one will return null objects whenever idBar is null:
#Entity
#Table(name = "VW_FOO_BAR")
class VwFooBar {
#EmbeddedId
private VwFooBarPk pk;
}
#Embeddable
class VwFooBarPk {
#Column(name="FOO_ID")
private Long idFoo;
#Column(name="BAR_ID")
private Long idBar
}
I'm accepting any solution that allows me to query the view, altough I prefer to avoid pure JDBC, if possible.
I'm using JPA 1/Hibernate 3.3 with the org.hibernate.dialect.Oracle9iDialect dialect.

Entity class with a composite primary key doesn't have getter and setter

I have an entity class userdetails which has the username, userid (numeric) and password fields, with username and userid forming a composite primary key. This is negotiable, and possibly unimportant to the main problem.
I have another class, connectiontable, which has userid as the primary key. The sql code used to generate the relevant tables is as follows:
create table usertable
(
userid int NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
username varchar(128) NOT NULL UNIQUE,
password varchar(128) NOT NULL,
CONSTRAINT USER_PK PRIMARY KEY(username, userid)
);
That's the sql code for usertable. The following is for connectiontable
create table connectiontable
(
userid int not null,
username varchar(128) not null,
connections varchar(32670) not null,
CONSTRAINT CONNECTION_PK PRIMARY KEY(username, userid),
CONSTRAINT CONNECTION_FK FOREIGN KEY(username,userid) REFERENCES usertable(username,userid)
);
There are a bunch of other things in connectiontable, but those are irrelevant. I use netbeans 7.2.1 and Jave EE6. I use the 'create entities from database entries' but for some reason, I don't have a getter and setter for either userid or username. They are in connectiontablePK, but I can't seem to make use of that. For example, when I generate the jsf pages, I want to be able to do something like:
Connectiontable con = new Connectiontable();
con.getUsername();
But it complains because it can't find that method in connectiontable.java.
Can anyone advise me why this is the case, and how I can solve it? Thank you.
... You're not posting the Java code, which I suspect would help but:
Anytime in JPA when you have a composite primary key, you have to have an 'embedded' primary-key class. I suspect you have a class definition similar to the following:
#Embeddable
public class UserNameId {
private int userid;
private String username;
}
And then usertable and connectiontable both contain the following (or similar):
#Embedded
#Id
private UserNameId userNameId;
... So you should expect a getter/setter for userNameId, but not the embedded fields, like you expect.

Hibernate #generatedvalue for HSQLDB

I have the following definition for an id field in an entity that is mapped to a table in HSQLDB.
...
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "ID")
private Integer id;
...
But this does not seem to generate the an unique id; instead an attempt is made to insert null into the column which results in failure. If, I manually create a sequence and generation strategy to use that sequence then the data is persisted as expected.
Doesn't a generation strategy of auto imply that the provider (hibernate in this case) will automatically choose the correct approach and do all the heavy lifting as needed (create sequence, use a native approach or whatever works for that particular platform)? Is my understanding incorrect?
Doesn't a generation strategy of auto imply that the provider (hibernate in this case) will automatically choose the correct approach and do all the heavy lifting as needed (create sequence, use a native approach or whatever works for that particular platform)? Is my understanding incorrect?
It does in theory (it defaults to IDENTITY with HSQLDB) and it works for me. This begs the following questions:
What dialect are you using (just in case)?
How did you create the table?
Can you show the DDL (activate the logging of org.hibernate.tool.hbm2ddl if required)?
How do you insert (through Hibernate's API, right?)?
Here is a sample DDL for an entity Foo when using HSQLDB:
create table Foo (
id bigint generated by default as identity (start with 1),
bar varchar(100),
primary key (id)
)
I created the table using the HSQL DB manager. Just normal create table address... I had not set the id column as identity in my case - just set it as primary key.
Then you have your answer, use an IDENTITY column.
While Hibernate does choose the right strategy and does generate the appropriate INSERT statements (passing null into the id which is expected to be persisted into an IDENTITY column), it won't create or alter your physical model if you don't use the DDL generation and export capabilities.
I had the same issue when using a JpaSchemaGenerator utility class that I wrote.
When generating the schema for a org.hibernate.dialect.HSQLDialect (where I use a SEQUENCE to generate my unique IDs), I use the following Hibernate property:
hibernate.id.new_generator_mappings=true
This results in the following CREATE statement:
CREATE TABLE BATCH (
BAT_ID NUMBER(19,0) NOT NULL,
BAT_EXPIRY_DATE TIMESTAMP,
BAT_NUMBER VARCHAR2(255 CHAR),
BAT_MAT_ID NUMBER(19,0),
PRIMARY KEY (BAT_ID)
);
But when I use this same property in my utility class to generate a schema using the org.hibernate.dialect.HSQLDialect, I get the following CREATE statement:
CREATE TABLE BATCH (
BAT_ID BIGINT NOT NULL,
BAT_EXPIRY_DATE TIMESTAMP,
BAT_NUMBER VARCHAR(255),
BAT_MAT_ID BIGINT,
PRIMARY KEY (BAT_ID)
);
This would mean that if I created a Batch without an ID, it would not generate it for me and the NOT NULL constraint would cause an exception.
If I change the Hibernate property to the following:
hibernate.id.new_generator_mappings=false
Then it would generate the following CREATE statement:
CREATE TABLE BATCH (
BAT_ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1),
BAT_EXPIRY_DATE TIMESTAMP,
BAT_NUMBER VARCHAR(255),
BAT_MAT_ID BIGINT,
PRIMARY KEY (BAT_ID)
);
Which works perfectly when creating JPA entities with Hibernate.

Categories