Hibernate 5.3.7: Unable to detect composite key - java

In our project, we have been using the hibernate xml configurations. Recently, I had upgraded my code from hibernate 3 to hibernate 5.3.7. Most of issues got resolved except below issue:
public class Employee implements Serializable{
private com.model.EmployeePK comp_id;
private String address;
private String phoneNumber;
// getter setters created and constructor is present for above fields
}
public class EmployeePK implements Serializable {
/** identifier field */
private Integer employeeName;
private Integer employeeNumber;
// getter setters created and constructor is present for above fields
}
I had been using below query while referring to this table to select records:
String hql = "from com.model.Employee aTable where
aTable.employeeNumber= :number ";
Here, in hibernate 3, this query works fine. However, in hibernate 5.3.7, this query throws an exception as hibernate not able to find employeeNumber( composite key) from the Employee entity.
To resolve the issue, i had to change the query and mention explicitly the composite key path in the query:
String hql = "from com.model.Employee aTable where
aTable.compId.employeeNumber= :number ";
Above query works fine in newer version.
I am putting this observation in forum to understand why I am seeing this behavior and is there any major changes made in the context which allows developer to explicitly declare composite keys in queries.
Is there any settings that can disable this behavior?

Related

SpringBoot JPA Repository - get selected fields shows error "Column not found"

I want to fetch selected columns, preferably dynamically.
For now, I'm specifying static columns - id, title, description.
Category.java
#Entity(name="Category")
#Table(name="categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String slug;
private String title;
private String description;
private String preview_image;
private int isFeatured;
/* Getters, setters etc */
}
CategoryRepository.java
public interface CategoryRepository extends JpaRepository<Category, Long> {
#Query(
value = "SELECT id, title, description FROM categories",
nativeQuery = true
)
List<Category> findAll();
}
This gives the error:
Column 'is_featured' not found. Exception class: class
org.springframework.dao.InvalidDataAccessResourceUsageException
Exception [could not execute query; SQL [SELECT id, title, description
FROM categories]; nested exception is
org.hibernate.exception.SQLGrammarException: could not execute query]
Any idea how this error can be resolved? I was thinking of using another model instead of Category but I need to make the fields dynamic later on. It's hard to make model class if I'm unaware of which fields to return.
Also, is there a way I can make this #Query code dynamic such that it returns columns mentioned in parameter?
Any help would be appreciated. Thanks in advance! :)
Its because of the naming strategy of Spring and Hibernate. It will convert camelCase to SNAKE_CASE by default. So in your case its isFeatured -> is_featured.
If you dont want to change the naming strategy just add #Column("isFeatured") on your property. This will override the default behavior of this property.
Here you can find more Spring Boot + JPA : Column name annotation ignored
Regarding dynamic query. You should look up the features in the documentation of Spring-data-jpa and about querydsl

JPA 2.1 persist string as enum postgres

I am using JPA 2.1 to perform CRUD operations on my DB. I generated entities from a pre-existing DB using JPA Tools -> Generate entities from Tables option. This created a Java entity named Item.
The DB table Item has a column of type enum item_status which was created as String type in the Java entity. Now I am trying to insert a column into table Item using the following method and got this error
ERROR: column "status" is of type item_status but expression is of type character varying. Hint: You will need to rewrite or cast the expression.
Item_Test_CRUD.java
// Here I am trying to test if I can insert into the DB
#Test
void test_insert_into_db() {
DBConnection conx = new DBConnection(); // here I get the message login successful, so I assume the connection to the DB is okay
Item it = new Item();
it.setStatus("Rework");
it.setUpdatedAt(new Timestamp(System.currentTimeMillis()));
it.setMetadata("{}");
conx.insert(it);
}
// I am using EntityManager to connect to the DB
DBConnection.java
public void insert(Object obj) {
this.em.getTransaction().begin();
this.em.persist(obj);
this.em.getTransaction().commit();
}
// Entity generated by the JPA tool
Item.java
#Entity
#Table(name="\"Items\"")
#NamedQuery(name="Item.findAll", query="SELECT i FROM Item i")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Convert(converter=UUIDConverter.class)
private String id;
.
.
#Column(name="status")
private String status;
.
.
}
Any idea what mistake I am doing here ?
Resolved the issue by adding stringtype=unspecified to the connection string. I didn't have to change the java entity column's type to enum. Just left it as String type.
For more information : stackoverflow.com/a/43125099/270371
https://jdbc.postgresql.org/documentation/94/connect.html

Mapping a Transient Property to an Alias - Spring JPA

Is it possible in Spring JPA to map a Transient property of an Object to an alias like so?
Native Query
SELECT *, 1 AS liked FROM User WHERE user_id = 123 // + logic to determine if liked
Class
#Entity
public class User {
#Id
private Long userId;
#Column(name = "displayName")
private String displayName;
#Transient
private int liked; // not tied to any column
}
I've tried to implement this but liked always returns 0 where it should be 1 (and null if I defined the field as an Object type)
Any help is appreciated!
You should use #Formula annotation for the field (see the example)
The #Formula annotation to provide an SQL snippet which Hibernate will execute when it fetches the entity from the database. The return value of the SQL snippet gets mapped to a read-only entity attribute.

Hibernate #SQLDelete sql not adding schema

I am trying to use the #SQLDelete annotation of Hibernate to make soft deletion. It works well when the DB schema is static, i.e: passing it in the SQL.
Unfortunately, it seems the SQL is passed as is to EntityPersisters (cf EntityClass's method CustomSQL createCustomSQL(AnnotationInstance customSqlAnnotation) so I can't find a way to pass the schema name dynamically like in Native SQL queries using {h-schema}
Did anyone find a good workaround for this issue (I am using Hibernate 4.3.5)?
Edit: Unless there is a real solution, I ended up modifying the code source of org.hibernate.persister.entity.AbstractEntityPersister by replacing the schema placeholder when setting the custom SQL queries in method doLateInit.
Edit2: I have created an issue for this behaviour in Hibernate JIRA. I will create a pull request later today and I wish the Hibernate Team will accept it
Soft deletes using Hibernate annotations.
As linked author stated below:
I am currently working on a Seam application that has a need for soft deletes in the database. To the right you can see a snippet of my database diagram which contains a CUSTOMER and APP_USER table. This is just a straight forward one to many relationship but the important thing to note though is the “DELETED” field in each table. This is the field that will be used to track the soft delete. If the field contains a ‘1’ the record has been deleted and if it contains a ‘0’ the record hasn’t been deleted.
Before ORMs like Hibernate I would have had to track and set this flag myself using SQL. It wouldn’t be super hard to do but who wants to write a bunch of boilerplate code just to keep track of whether or not a record has been deleted. This is where Hibernate and annotations comes to the rescue.
Below are the 2 Entity classes that were generated by Hibernate using seamgen. I have omitted parts of the code for clarity.
Customer.java
//Package name...
//Imports...
#Entity
#Table(name = "CUSTOMER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE customer SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class Customer implements java.io.Serializable {
private long id;
private Billing billing;
private String name;
private String address;
private String zipCode;
private String city;
private String state;
private String notes;
private char enabled;
private char deleted;
private Set appUsers = new HashSet(0);
// Constructors...
// Getters and Setters...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
// Filter added to retrieve only records that have not been soft deleted.
#Where(clause = "deleted <> '1'")
public Set getAppUsers() {
return this.appUsers;
}
public void setAppUsers(Set appUsers) {
this.appUsers = appUsers;
}
}
AppUser.java
//Package name...
//Imports...
#Entity
#Table(name = "APP_USER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE app_user SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class AppUser implements java.io.Serializable {
private long id;
private Customer customer;
private AppRole appRole;
private char enabled;
private String username;
private String appPassword;
private Date expirationDate;
private String firstName;
private String lastName;
private String email;
private String phone;
private String fax;
private char deleted;
private Set persons = new HashSet(0);
// Constructors...
// Getters and Setters...
}
The following 2 steps is all that I had to do to implement the soft delete.
Added the #SQLDelete annotation which overrides the default
Hibernate delete for that entity.
Added the #Where annotation to filter the queries and only return
records that haven’t been soft deleted. Notice also that in the
CUSTOMER class I added an #Where to the appUsers collection. This is
needed to fetch only the appUsers for that Customer that have not
been soft deleted.
Viola! Now anytime you delete those entities it will set the “DELETED” field to ‘1’ and when you query those entities it will only return records that contain a ‘0’ in the “DELETED” field.
Hard to believe but that is all there is to implementing soft deletes using Hibernate annotations.
Note:
also note that instead of using the #Where(clause="deleted ‘1’") statements you can use hibernate filter (http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-filters) to globally filter-out all ‘deleted’ entities. I found that defining 2 entity managers (‘normal’ one that filter deleted items, and one that doesn’t, for the rare cases…) is usually quite convenient.
Using EntityPersister
You can create a DeleteEventListener such as:
public class SoftDeleteEventListener extends DefaultDeleteEventListener {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public void onDelete(DeleteEvent event, Set arg1) throws HibernateException {
Object o = event.getObject();
if (o instanceof SoftDeletable) {
((SoftDeletable)o).setStatusId(1);
EntityPersister persister = event.getSession().getEntityPersister( event.getEntityName(), o);
EntityEntry entityEntry = event.getSession().getPersistenceContext().getEntry(o);
cascadeBeforeDelete(event.getSession(), persister, o, entityEntry, arg1);
cascadeAfterDelete(event.getSession(), persister, o, arg1);
} else {
super.onDelete(event, arg1);
}
}
}
hook it into your persistence.xml like this
<property name = "hibernate.ejb.event.delete" value = "org.something.SoftDeleteEventListener"/>
Also, don't forget to update your cascades in your annotations.
Resource Link:
Hibernate: Overwrite sql-delete with inheritace
Custom SQL for CRUD operations
Custom SQL for create, update and delete
Use like this
#SQLDelete(sql = "UPDATE {h-schema}LEAVE SET STATUS = 'DELETED' WHERE id = ?", check = ResultCheckStyle.COUNT)
I think there are 2 way
First is to add:
app.datasource.schema=<schema_name>
to your application.properties.
The second is to use the schema in annotation to your table model

Is there any Simplest way to get Table metadata (column name list) information, in Spring Data JPA ? which could I use on universal database

I am in a situation where I want to get all table's column list using spring data jpa, my database is flexible so, the query should be work on all kind of database.
JPA specification contains the Metamodel API that allows you to query information about the managed types and their managed fields. It does not however cover the underlying database. So, there is nothing out-of-the-box in JPA yet for querying the database metadata.
The way each RDBMS stores meta information is also different so there cannot be a simple, database-agnostic solution.
What you want can however be achieved through a few hops.
Step 1: Define an entity class that will hold metadata information.
#Entity
#IdClass(TableMetadataKey.class)
#Table(name = "table_metadata")
class TableMetadata {
#Column(name = "column_name")
#Id
String columnName;
#Column(name = "table_name")
#Id
String tableName;
public static class TableMetadataKey implements Serializable {
String columnName;
String tableName;
}
}
Step 2: Add the repository for the entity.
public interface TableMetadataRepository extends JpaRepository<TableMetadata, TableMetadataKey>
{
TableMetadata findByTableName(String tableName);
}
Step 3: Define a database view named table_metadata to be mapped to the entity class. This will have to be defined using a database-specific query (because each database has a different way of storing its metadata).
Database-specific optimizations can be performed on this step, such as, using materialized views with Oracle for faster access, etc.
Alternatively, a table named table_metadata can be created with the required columns and populated periodically using a SQL script.
Now the application has full access to the required metadata.
List<TableMetadata> metadata = tableMetadataRepository.findAll()
TableMetadata metadata = tableMetadataRepository.findByTableName("myTable");
One issue to be noted is that not all tables in a schema may be mapped as JPA entities or not all columns in all tables may be mapped as entity fields. Therefore, directly querying the database metadata may give results that do not match the entity classes and fields.
You can get the Column name list using your Entity or Model. What we need is #Column, which should be used in your Entity. You will get all the details which you have specified in #Column. All the parameters are Optional, although it is good to define all.
#Column(name, columnDefinition, insertable, length, nullable,
precision, scale, table, unique, updatable)
We can get all fields declared in Entity by User.class.getDeclaredFields() ( in general ModelName.class.getDeclaredFields()). After getting all feilds we can get particular Column using field.getAnnotation(Column.class) we can also get all the details specified in #Column as below
Columns: #javax.persistence.Column(nullable=false, precision=2, unique=true, name=id, length=2, scale=1, updatable=false, columnDefinition=, table=, insertable=true)
Columns: #javax.persistence.Column(nullable=true, precision=0, unique=false, name=client_id, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
Columns: #javax.persistence.Column(nullable=true, precision=0, unique=false, name=firstname, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
Columns: #javax.persistence.Column(nullable=true, precision=0, unique=false, name=lastname, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
create endPoint or method as per requirement
#GetMapping(value= "/columns/name")
public List<String> tableColumnsName()
{
List<String> Columns = new ArrayList<String>();
Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
Column col = field.getAnnotation(Column.class);
if (col != null) {
Columns.add(col.name());
System.out.println("Columns: "+col);
}
}
return Columns;
}
Entity/Model
#Entity
#Table
public class User {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
#Column(name="id")
public int id;
#Column(name="client_id")
private int clientId;
#Column(name="firstname")
private String firstname;
#Column(name="lastname")
private String lastname;
//AllArgConstructor-Constructor
//Getters-Setters
}
Tested via Postman
SchemaCrawler has a Java API that allows you to work with database metadata in a general way, this is, without caring about the specific database platform.
http://www.schemacrawler.com
The above solution works for "simple primary key". But for "composite primary key", the solution is mentioned below
BuidingKey --> Composite Primary key
#GetMapping("/columns")
public List<String> getColumns() {
List<String> entityColumns = Arrays.asList(Building.class.getDeclaredFields()).stream().map(Field::getName)
.collect(Collectors.toList());
List<String> entityCompositePKColumns = Arrays.asList(BuidingKey.class.getDeclaredFields()).stream().map(Field::getName)
.collect(Collectors.toList());
entityCompositePKColumns.addAll(entityColumns);
return entityCompositePKColumns;
}

Categories