Criteria API createAlias on column containing null values kills the query - java

I do have a simple entity named "Address" that has a couple of properties and relations defined by itself and some inherited from some superclass.
public class Base {
private UUID id;
private Date createdAt;
#NotNull
private User createdBy; // User is a related Entity that cannot be null
private DataImport dataImport; // DataImport is another related entity that can be null
//getter & setter
}
public class Address extends Base {
private String street;
#NotNull
private Country country; // related entity that can't be null
//getter & setter
}
What I'm trying to achieve is with one query using the Criteria API, I want to get a list of Address objects containing all simple attributes like street and createdAt. At the same time I want only the IDs of the related entities if present: createdBy.id, dataImport.id and country.id.
I'm almost there using the following Criteria query:
entityManager.getDelegate().createCriteria(Address.class).add(criteriaExample)
.excludeZeroes()
.createAlias("createdBy", "subcreatedBy")
.createAlias("country", "subcountry")
.setProjection(Projections.projectionList().add(Projections.property("id").as("id"))
.add(Projections.property("createdAt").as("createdAt"))
.add(Projections.property("street").as("street")).
.add(Projections.property("subcreatedBy.id").as("createdBy.id"))
.add(Projections.property("subcountry.id").as("country.id")))
.setResultTransformer(new AliasToBeanNestedResultTransformer(Address.class));
List<Address> result = criteria.list();
This works just perfect!
Problem occurs when I only add the "alias" for the dataImport relation.
...createAlias("dataImport", "subdataImport")...
Even without adding the Projection for dataImport.id to the query, it returns an empty list, meaning list.size() = 0, as soon as I add this alias.
My current guess is, that I can't put an alias on a nullable property. Does anybody have an idea what the solution might be? So, when the related entity is not null, I want to get it's ID. And I want it to be simply null, when the relation is not set.
Thanks in advance.

Stupid me should have read the documentation and set the CriteriaSpecification.LEFT_JOIN.
...createAlias("dataImport", "subdataImport", CriteriaSpecification.LEFT_JOIN)...

Related

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.

Using JPA to access a view without a unique id returns null

I am using JPA and have a view I would like to access. I am using a mapped entity with an embedded Id in many of my other classes to access tables with similar requirements. However here whenever there are nulls in the view that comprise the id, the whole object is returned as null. There are the right number of entities returned when i query, but they are null.
Here are the classes:
{
#Entity
#Table(name = "VW_PRODUCT")
public class VwProduct implements Serializable {
#EmbeddedId
private VwProductId id;
public VwProduct() {
}
}
{
#Embeddable
public class VwProductId implements java.io.Serializable {
#Column(name = "PROD_NAME", nullable=true)
private String prodName;
#Column(name = "PROD_CTGRY", nullable=true)
private String prodCtgry;
#Column(name = "PROD_SBCTGRY", nullable=true)
private String prodSbctgry;
}
I omitted things like getters and setters and hashcode but i think my question is clear; how do I access this view, when some of its values are null?
Thank you!
If you have a primary key column with null, and search on that column will always return no objects.
There are three possible solutions that I am aware of, in order of complexity/wonkiness. (For people not working on a read-only view, do not do any of the following. You will blow your foot off with an incredibly large shotgun.)
The easiest answer is to change your definition of the view and add something like a rowid or generated serial and then make that the primary key.
The second answer, and this is both implementation specific and hibernate specific, is to have a primary key of #ID #ROWID String id;
The last answer, is more complex, is by mapping all three of your fields to a "NullableString" UserType, and have a nullSafeGet that maps null to something non-null, like '' or something. You'll get duplicates, but since this is a read-only view, you don't really care.

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;
}

How to assign a column name of Spring Model from a variable

I have a Sring hibernate Model as follows
#Entity
#Table(name = "client")
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "id")
private long id;
private String type;
...
...
...
I have some 50 columns. Now while inseting a new row into this table, how can i give the column name dynamically,
Client client = new Client();
String columnName = "type";
How do I update client model with the column name given in string columnName?
If you want to set the values for columns dynamically, you can use Java reflection concept.
Have a look at the Java reflection concept, following links may be helpful
http://tutorials.jenkov.com/java-reflection/fields.html
http://www.avajava.com/tutorials/lessons/how-do-i-get-and-set-a-field-using-reflection.html
For updating the table, we need to get the the field from the category Object and set the new value for that and finally invoke the save() method.
But if you want to set the field values dynamically, then you need to find the the attributes present inside the Category object using Java reflection methods. Use methods provided by Field object in java and use them. Then after getting the filed invoke the set method of the same and update the value.
Category.class.getDeclaredFields().

Removing Many to one relationship in JPA using merge()

I have the below unidirectional Many To One mapping
#Entity
public class Item implements Serializable {
private Integer id;
private Double amount;
private Country origin;
#ManyToOne(optional=true)
#JoinColumn
public Country getOrigin() {
return this.origin;
}
}
#Entity
public class Country implements Serializable{
private String code;
private String desc;
}
Let say the relationship is optional so I am trying to remove the relation by updating it to null using code below
Country country = null;
//item is detached
item.setOrigin(country);
em.merge(item);
But the result turns out to be relationship is not removed.
However, this code works fine if country is not null and the system can update the relationship in DB.
It just simply ignore the field if it's null.
Can someone points out what setting can be changed in order to achieve my desired result?
P.S. Please be reminded that I am not wanting to delete the entity Country, but just remove the relationship between them.
Thanks all it's a mistaken question. It actually works.
There's just some client side issue submitting wrong data to it.

Categories