JPA composite primary key with null value - java

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.

Related

JPA/Hibernate on Oracle: Skip autogenerated ID Column when I persist the entity

Info: Oracle DB 19. Hibernate is v5.5.4 with org.hibernate.dialect.Oracle12cDialect. JDBC driver is v12.2.0.1.
Question:
I want to save an entity with JPA/Hibernate (DB is Oracle 11g), that has an autogenerated ID column (here: PROT_ID).
CREATE TABLE SOME_PROTOCOL(
PROT_ID NUMBER(18) GENERATED ALWAYS AS IDENTITY (START WITH 123 MAXVALUE 99999) NOT NULL,
MORE_COLS VARCHAR2(500 CHAR) NOT NULL);
To add a new record, I have to skip the ID column, like that:
insert into SOME_PROTOCOL (MORE_COLS) values ('Some Value');
This is my Entity class:
#Entity
#Table(name = "SOME_PROTOCOL")
public class SomeProtocol {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PROT_ID", insertable = false)
private Long id;
// Getter, Setter, other columns
}
Saving the entity with
SomeProtocol s = new SomeProtocol();
s.setMoreCols("whatever");
hibernateSession.save(s);
leads to this error:
ERROR: Invalid argument(s) in call
Hibernate: insert into APPL_PROTOCOL (PROT_ID, MORE_COLS) values (default, ?)
Ok, JPA doesn't skip the ID column, but sets default as a value.
I tried some more with #Column(insertable=false) or GenerationType.AUTO, but to no avail.
How can I save an entity class with an autogenerated ID column?
Solution:
We changed the ID generation for that table, we now use an external sequence (previously it was auto-generated). Hibernate can save the entity now via hibernateSession.save.
#SequenceGenerator(name = "SEQ_APPL_PROT_GENERATOR", sequenceName = "SEQ_APPL_PROTOCOL_ID", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_APPL_PROT_GENERATOR")
#Column(name = "PROT_ID")
private Long id;
I think the insertable = false in your #Column annotation might be the problem. Please try without that and let us know how that works.
You can read more about this attribute on Please explain about insertable=false and updatable=false in reference to the JPA #Column annotation.

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.

How to add two ID Sequence Auto Increment In Postgres And Mapping With Hibernate

I'm Developing small MicroService Using Spring boot.
My database Is Postgres and I have a pojo in my service mapping the table.
Initially my Table is created with one Primary Key NOT NULL serial.
Now I want to add One more column with serial number starting from 6000 and map with my pojo.
#JsonProperty("id")
#Column(name = "id", nullable = false)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY,generator = "order_seq_gen")
#SequenceGenerator(name = "order_seq_gen", sequenceName ="order_id_seq")
private Integer id;
#JsonProperty("state")
private String state;
#JsonProperty("user_Id")
#Column(name = "user_Id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY,generator = "user_seq_gen")
#SequenceGenerator(name = "user_seq_gen", sequenceName ="user_id_seq")
private Integer userId;
This is My Pojo.
The first Id Is creating but the userId Is not creating.
Bellow is my Table structure and sequences.
This is my create table statement
CREATE TABLE public.user
(
id integer NOT NULL DEFAULT nextval('user_id_seq'::regclass),
created timestamp without time zone,
modified timestamp without time zone,
state character varying(255),
name text,
user_id integer NOT NULL DEFAULT nextval('user_id_seq'::regclass), CONSTRAINT user_pkey
PRIMARY KEY (id)
)
Below is my sequence.
CREATE SEQUENCE public.user_id_seq INCREMENT 1 MINVALUE 6001
MAXVALUE 9223372036854775807 START 6000 CACHE 1;
ALTER TABLE public.user_id_seq OWNER TO postgres;
What I'm trying is in my table the column user_id should generate Auto from Base as 6000 and increment each time by 1.
Whenever I call API from my service it is creating new record. But the user_id is not showing anything.

How to insert entity with foreign keys defined as Long in Hibernate?

I'm using Hibernate 3.6.8 and I have a table defined in mysql (5.5) like this:
CREATE TABLE mytable
(
id BIGINT(20) PRIMARY KEY NOT NULL,
version INT(11),
description VARCHAR(60),
scheduled_at DATETIME,
`from` BIGINT(20),
`to` BIGINT(20),
deleted BIT(1) DEFAULT b'0',
completed BIT(1) DEFAULT b'0',
delete_from_after_completion BIT(1) DEFAULT b'0',
CONSTRAINT FK14F71A73C588I54F FOREIGN KEY (`from`) REFERENCES other_table (id),
CONSTRAINT FK14F71C231C45J198 FOREIGN KEY (`to`) REFERENCES other_table (id)
);
CREATE INDEX FK14F71A73C588I54F ON mytable (`from`);
CREATE INDEX FK14F71C231C45J198 ON mytable (`to`);
And a Java entity defined like this:
#Entity
public class MyTable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#Column(name = "description")
private String description;
#Column(name = "scheduled_at")
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "SM")
private Date scheduledAt;
#Column(name = "from")
private Long fromId;
#Column(name = "to")
private Long toId;
#Column(name = "deleted")
private boolean deleted;
#Column(name = "completed")
private boolean completed;
#Column(name = "delete_from_after_completion")
private boolean deleteFromEntityAfterCompletion;
...
}
When I try to persist an instance of MyTable with valid values I end up with the following error:
2016-01-12 10:06:33,443 [qtp2139431292-20] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 1064, SQLState: 42000
2016-01-12 10:06:33,443 [qtp2139431292-20] ERROR org.hibernate.util.JDBCExceptionReporter - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'from, scheduled_at, to, version) values (0, 1, 0, 'Test', 10, '2016-01-13 00:00:' at line 1
I suspect that the problem occurs since I try to model the foreign key relationships (to and from) as Long instead of an entity (OtherTable). I suspect this because Hibernate can indeed persist this entity if I comment out the to and from fields. Note that the relationship to the to and from entities does indeed exists in the database so that's not the problem.
If I try insert manually using something like this it works:
insert into mytable values(3, 0, "desc", '2016-10-10 00:00:', 10, 11, 0, 0, 0);
You have a column that is a SQL reserved keyword ("from") and Hibernate doesn't bother quoting it for you. Other JPA implementations (e.g DataNucleus JPA) take care of such things for you. You will have to add single quotes around the reserved word in your JPA annotation information
I am pretty sure that the problem is not a matter of persisting Long values.
Your code has something wrong. I bet there is a wrong quotation mark in your insert code.

Hibernate: To return sum of specific column grouped by date

I need to write a criteria query to retrieve a sum of amount column grouped by a specific date. The table I mean consist of the following columns:
id partner_id amount date platform_id
serial integer numeric(13.4) timestamp integer
Hibernate mapping does in the following way:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(targetEntity = Partner.class, fetch = FetchType.EAGER)
#JoinColumn(name="partner_id")
private Partner partner;
#Column(name = "amount")
private BigDecimal amount;
#Column(name = "date")
private Date date;
#ManyToOne(targetEntity = Platform.class, fetch = FetchType.EAGER)
#JoinColumn(name="platform_id")
private Platform platform;
I've written the following query:
Criteria criteria = getSession().createCriteria(DailyProfit.class)
.add(Restrictions.eq("partner", partner))
.add(Restrictions.eq("platform", platform))
.add(Restrictions.between("date", from, to));
criteria.setProjection(Projections.groupProperty("date"));
criteria.setProjection(Projections.sum("amount").as("DailySum"));
But the criteria.list() returns [1119950.8300, null, null, null, null, null, null, null, null, null]. That is absolutely not what I'm expected. Could you explain that result and help me to fix criteria query?
Your code was perfect
criteria.list() - will return List of object (Collections). You should use index or iterator to get the value of result.
Or
Integer amount= (Integer) criteria.uniqueResult();

Categories