JPA repository retrieve custom primary key value after save - jpa

I have an entity class pointing to postgresql table. Below is table structure. The paymentreferencenumber is the PK which is populated by a trigger. id field is the sequence generated field. When i try to save in this table using JPARepository save method it inserts the first record. But after that it fails due to the primary key constraint. Since PK is a string type and generated using trigger I am specifying generator strategy as 'select'. Can anyone help me with this blocker and point me in the right direction. Thanks
Table structure --
custId serial not null,
paymentreferencenumber varchar(32) not null
constraint customers1_pkey
primary key,
firstname varchar(255),
lastname varchar(255)
Entity class --
#Entity
#Table(name = "customersnew")
public class Customer implements Serializable {
private static final long serialVersionUID = -1L;
#GeneratedValue(generator = "seq")
#GenericGenerator(name="seq", strategy="sequence", parameters = { #Parameter(name="key", value = "customersnew_custid_seq")})
#Column(name = "custid")
private long id;
#Id
#GeneratedValue(generator = "trigger_generated")
#GenericGenerator(name="trigger_generated", strategy="select", parameters = { #Parameter(name="key", value = "id")})
#Column(name = "paymentreferencenumber")
private String refNum;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
}
--- Controller using JPA save
#RestController
public class CustomerController {
#Autowired
CustomerRepository repository;
EntityManagerFactory emf;
public CustomerController(CustomerRepository repository, EntityManagerFactory emf) {
this.repository = repository;
this.emf = emf;
}
#PostMapping("/create")
public String create(#RequestBody CustomerUI customer){
// save a single Customer
Customer returnObj = repository.saveAndFlush(new Customer(customer.getFirstName(), customer.getLastName()));
PersistenceUnitUtil util = emf.getPersistenceUnitUtil();
Object retObj = util.getIdentifier(returnObj);
return "Customer is created";
}

If you don't specify an id generation strategy, Hibernate will use GenerationType.AUTO. This will result in any of
AUTO - either identity column, sequence or table depending on the
underlying DB.
If you look here, you'll notice all of those generate ids of type long, short or int, not of type String.
Say you wanted a String UUID as an id, you could use
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "paymentreferencenumber")
private String refNum;

Related

org.postgresql.util.PSQLException: ERROR: null value in column "id" violates not-null constraint, Id Colums not able generate by sequcece in Hibernat

I am getting an error that Id column is not able to generate.
which will have the Getters and Setters.
Note: In my case, Primary Key was bookingId.
"Id" has to be a sequence number.
Below is the save method.
At the time of save is was not able to generate value for the column.
Below is My Entity.
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Id
#Column(name = "booking_id")
private String bookingId;
#Column(name = "booked_by")
private String bookedBy;
#Column(name = "booking_status")
private int bookingStatus;
#Column(name = "booking_rcv")
private Date bookingRcv;
#Column(name = "chargeable_kgs")
private BigDecimal chargeableKgs;
}
Here we will have all the getters and setters, Constructors, hashcode, and equals methods.
#Transactional
#Override
public Booking saveBooking(Booking booking) {
Booking savedBooking = bookingRepository.save(booking);
if (savedBooking.getId() != null) {
booking.setId(savedBooking.getId());
}
booking.setVersion(savedBooking.getVersion());
bookingRepository.flush();
// bookingRepository.refresh(booking);
LOGGER.debug("savedbooking id::" + savedBooking.getId());
return savedBooking;
}
I am using postgre DB and Hibernate.

JPA how to set foreign id - in Embeddable id - that is generated

My use case is: I have a Product with multi language name. To have at most one name per language a translation should be identified by locale + productId.
My problem is to get it working with a generated productId. This are the entities:
e.g. in oracle i get: "ORA-01400: cannot insert null into ."PRODUCT_NAME"."FOREIGN_ID""
Product:
#Entity
#Data
public class Product {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String articleNumber;
/**
* Localized name of the product.
*/
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#MapKey(name = "nameId.locale")
private Map<Locale, ProductName> names = new HashMap<>();
}
ProductName:
#Entity
#Data
public class ProductName {
#EmbeddedId
private TranslationId nameId;
private String name;
}
TranslationId:
#Embeddable
#Data
public class TranslationId implements Serializable {
private static final long serialVersionUID = 3709634245257481449L;
#Convert(converter = LocaleConverter.class)
private Locale locale;
private Long foreignId; //<-- this is null, should reference product
}
Is there a way to get this working without having to save the product first without the name? Without a generated id it is working of course - i just set same id for both.
I would like to re-use the translation id for other translated fields of other entities.

How to reference a Composite Secondary Key

The below examples show what I tried to reference an entity by a unique combination of columns that is not its primary key.
I want to do it that way because the referencing table contains all the necessary constituents of the referenced "Composite Secondary Key" but it does not contain the Primary Key.
How can I achieve this? (If possible without being Hibernate specific)
Base example
Suppose an entity has a primary key as well as a composite secondary key:
#Entity
#Table(name = "EXAMPLE_DATA",
uniqueConstraints = {
#UniqueConstraint(columnNames = {
"COMPOSITE_KEY_PART_1",
"COMPOSITE_KEY_PART_2"})
})
public class ExampleData {
#Id
#Column
private Long exampleDataId;
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1;
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2;
}
I would like to reference it from another table by its composite secondary key:
#Entity
#Table(name = "EXAMPLE")
public class Example {
#Id
#Column
private Long exampleId;
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1; // of ExampleData
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2; // of ExampleData
#MayToOne
#JoinColumn(name = "COMPOSITE_KEY_PART_1", insertable = false, updatable = false)
#JoinColumn(name = "COMPOSITE_KEY_PART_2", insertable = false, updatable = false)
private ExampleData exampleData;
}
However, this leads to
org.hibernate.AnnotationException:
A Foreign key refering com.example.ExampleData from com.example.Example has the wrong number of column. should be 1
Making a separate #Embeddable composite key
I tried making the secondary key embeddable
#Embeddable
public class CompositeKey implements Serializable {
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1;
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2;
}
and using it as an embedded object:
#Entity
#Table(name = "EXAMPLE_DATA",
uniqueConstraints = {
#UniqueConstraint(columnNames = {
"COMPOSITE_KEY_PART_1",
"COMPOSITE_KEY_PART_2"})
})
public class ExampleData {
#Id
#Column
private Long exampleDataId;
#Embedded
private CompositeKey compositeKey;
}
but that leads to the same exception:
org.hibernate.AnnotationException:
A Foreign key refering com.example.ExampleData from com.example.Example has the wrong number of column. should be 1
Using #EmbeddedId
Using #EmbeddedId instead of just #Embedded leads to issues with multiple keys
org.hibernate.AnnotationException:
com.example.CompositeKey must not have #Id properties when used as an #EmbeddedId: com.example.ExampleData.compositeKey
Having only a single key works but is undesirable
The only way I can actually make it work is by removing the primary key and making the composite key the primary key (but I don't want that)
#Entity
#Table(name = "EXAMPLE_DATA")
public class ExampleData {
// #Id // removing this primary key is undesirable
#Column
private Long exampleDataId;
#EmbeddedId // This now becomes the primary key
private CompositeKey compositeKey;
}

Hibernate Filter being ignored

My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).

Mapping JPA entity relationships

I would like some advice on how to best layout my JPA entity classes. Suppose I have 2 tables I would like to model as entities, user and role.
Create Table users(user_id primary key,
role_id integer not null )
Create table role(role_id primary key,
description text,
)
I create the following two JPA Entities:
#Entity
#Table(name="users")
#Access(AccessType.PROPERTY)
public class User implements Serializable {
private Long userId;
private Long roleId;
private Role role;
#Column(name = "user_id")
#Id
public Long getUserId() {}
#Column(name = "role_id")
public Long getRoleId() {}
#ManyToOne()
JoinColumn(name="role_id")
public Role getRole() {}
}
Role Entity:
#Entity
#Table(name="Role")
#Access(AccessType.PROPERTY)
public class Role implements Serializable {
private String description;
private Long roleId;
#Column(name = "role_id")
#Id
public Long getRoleId() {}
#Column(name = "description")
public Long getDescrition(){}
#ManyToOne()
#JoinColumn(name="role_id")
public Role getRole() {}
}
Would the correct way to model this relationship be as above, or would I drop the private Long roleId; in Users? Any advice welcomed.
When I map it this way, I receive the following error:
org.hibernate.MappingException: Repeated column in mapping for entity:
Yes, you would drop the private Long roleId mapping when you have a #ManyToOne on the same column.
As the error implies, you can only map each column in an #Entity once. Since role_id is the #JoinColumn for the #ManyToOne reference, you cannot also map it as a property.
You can, however, add a convenience method to return the role ID, like
public Long getRoleId() {
return role.getId();
}