Can JPA be persuaded to convert between eg UUIDs and Strings? - java

I have a Java object with a field which is a UUID. I'd like to be able to persist this object to the database in the obvious way; however, the Basic mapping will use Java serialization to write it, while I want the UUID to be present in its obvious string form. Is there a way to supply a UUID <-> String converter to JPA for that field which will be used at read and write time so I can handle this type naturally?

Chris Lercher commented Note: Starting from JPA 2.1, a #Convert annotation can be used with an AttributeConverter<UUID, String>.
This approach works well and is compatible with any JPA provider, whereas the #Type(type = "uuid-char") is provider specific.
Also, with autoApply=true is applied to every field of every entity, so there is no need to annotate each field in each entity. See the documentation here and check the example below:
The converter class
#Converter(autoApply = true)
public class UuidConverter implements AttributeConverter<UUID, String> {
#Override
public String convertToDatabaseColumn(final UUID entityValue) {
return ofNullable(entityValue)
.map(entityUuid -> entityUuid.toString())
.orElse(null);
}
#Override
public UUID convertToEntityAttribute(final String databaseValue) {
return ofNullable(databaseValue)
.map(databaseUuid -> UUID.fromString(databaseUuid))
.orElse(null);
}
}
The entity
#Entity
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column
private String name;
#Column(nullable = false, unique = true, updatable = false, columnDefinition="CHAR(36)")
private UUID customerId = randomUUID();
//.....
}
And this is how it looks in the database
TABLE customer
ID BIGINT(19) NO PRI (NEXT VALUE FOR SYSTEM_SEQUENCE_5D3)
NAME VARCHAR(255) YES NULL
CUSTOMER_ID VARCHAR(36) NO UNI NULL

JPA 2.0 doesn't provide a general way to do it, except for creating separate getters/setters for different representations of the same field.
Depending on your JPA provider you can use implementation-specific methods, for example, Hibernate provides a uuid-char type for this purpose:
#Type(type = "uuid-char")
private UUID uuid;

You could annotate your UUID property #Transient and at the same time provide its persistable String-based representation.
During #PrePersist, #PreUpdate or #PostLoad you'll set this String-based representation basing on UUID or (in case of loading it from the DB) re-create your UUID from the String.

I am not aware of the JPA itself. There is a way using Hibernate if it is your ORM provider.
Hibernate 5.x
Use the #Type annotation with "uuid-char" to map into the VARCHAR column.
#Type(type="uuid-char")
private UUID uuid;
Documentation: JBoss Hibernate 5.6 User Guide | 2.3. Basic types
Hibernate 6.x
As of Hibernate 6.x, you can use the following configuration to map them automatically:
hibernate.type.preferred_uuid_jdbc_type=CHAR
Documentation: JBoss Hibernate 6.2 User Guide | 2.2.38. UUID

Related

Can we use Composite Primary Key Mapping in spring data elastic search

I have an entity 'Product' and I want the primary key in ES to be used as a combination of 'id' and 'name' attributes. How can we do that using spring data elastic search.
public class Product {
#Id
private String id;
#Id
private String name;
#Field(type = FieldType.Keyword)
private Category category;
#Field(type = FieldType.Long)
private double price;
#Field(type = FieldType.Object)
private List<ValidAge> age;
public enum Category {
CLOTHES,
ELECTRONICS,
GAMES;
}
}
One way to achieve this would be the following:
first rename your id property, I changed it to documentId here. This is necessary, because in Spring Data
Elasticsearch an id-property can be either annotated with #Id or it can be namend id. As there can only be one
id-property we need to get this out of the way. It can have the name id in Elasticsearch, set by the #Field
annotation, but the Java property must be changed.
second, add a method annotated with #Id and #AccessType(AccessType.Type.PROPERTY) which returns the value you
want to use in Elasticsearch.
third, you need to provide noop-setter for this property. This is necessary because Spring Data Elasticsearchsoe
not check the id property to be read only when populating an entity after save or when reading from the index.
This is a bug in Spring Data Elasticsearch, I'll create an issue for that
So that comes up with an entity like this:
#Document(indexName = "composite-entity")
public class CompositeEntity {
#Field(name="id", type = FieldType.Keyword)
private String documentId;
#Field(type = FieldType.Keyword)
private String name;
#Field(type = FieldType.Text)
private String text;
#Id
#AccessType(AccessType.Type.PROPERTY)
public String getElasticsearchId() {
return documentId + '-' + name;
}
public void setElasticsearchId(String ignored) {
}
// other getter and setter
}
The repository definition would be straight forward:
public interface CompositeRepository extends ElasticsearchRepository<CompositeEntity,
String> {
}
Remember that for every method that needs an Elasticsearch Id, you'll need to create like it's done in the entity
class.
I am not sure about spring data elasticsearch but spring jpa provides the facility of defining composite primary key by using #IdClass where we can define a separate class(let us say class A) in which we can define all the fields which we want to be a part of composite key Then we can use #IdClass(A.class) in entity class and use #Id annotation on all the fields which should be the part of the composite key
you can refer to this article, although I am not sure whether the same concept will be applicable for spring data es - https://www.baeldung.com/jpa-composite-primary-keys

Use WHERE in JPA to query the entity

I have this entity:
#Entity
#Table
public class Terminals implements Serializable {
private static final long serialVersionUID = 5288308199642977991L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column
private int merchant_id;
#Column
private String terminalToken;
.....
}
I tried to use this query:
public Terminals getTerminalToken(String terminalToken) throws Exception {
return entityManager.find(Terminals.class, terminalToken);
}
Looks like it's selecting only the table key.
How I can select the table column terminalToken?
You better use Spring data to build your queries along with JPA Repositories. You will just need to extend the JpaRepository interface, and follow the naming conventions to name your methods.
Your method will look like this:
public List<Terminal> findByTeminalToken(String TerminalToken);
Otherwise you will need to use entityManager.createQuery() method instead of entityManager.find() because the latter one is only used with the id column.
If you are looking for pure java (i am more in favour of Philipp solution) perhaps you wish to check out this solution. https://www.objectdb.com/java/jpa/query/criteria. Sorry for not posting a direct solution but i think it woths more to give you the source.
By the way, why not using spring data? Much easier

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.

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

What exactly represent this "Hibernate" class mapping?

I am working on a Java web application that I think use Hibernate and I am not so into Hibernate so I have the following doubt:
I have a model class named ReaDichiarazioneIntento that map a database table named REA_DICHIARAZIONE_INTENTO, something like this:
#javax.persistence.IdClass(it.enel.wearea.entity.ReaDichiarazioneIntentoPK.class)
#javax.persistence.Table(name = "REA_DICHIARAZIONE_INTENTO", schema = "EDIWEA")
#Entity
public class ReaDichiarazioneIntento implements Cloneable {
private Integer idDichiarazione;
#javax.persistence.Column(name = "ID_DICHIARAZIONE")
#Id
public Integer getIdDichiarazione() {
return idDichiarazione;
}
public void setIdDichiarazione(Integer idDichiarazione) {
this.idDichiarazione = idDichiarazione;
}
private Integer idCliente;
#javax.persistence.Column(name = "ID_CLIENTE")
#Basic
public Integer getIdCliente() {
return idCliente;
}
public void setIdCliente(Integer idCliente) {
this.idCliente = idCliente;
}
...................................................................
...................................................................
...................................................................
SOME OTHER FIELDS AND RELATED GETTER AND SETTER METHODS
...................................................................
...................................................................
...................................................................
}
Ok I have some doubts about this class. My doubt are:
1) Is it using Hibernate for mapping the class to the database table? Or what? I know that to map a database table to a class I have to do something like:
#Entity
#Table(name = "REA_DICHIARAZIONE_INTENTO")
Why in this project do:
#javax.persistence.IdClass(it.enel.wearea.entity.ReaDichiarazioneIntentoPK.class)
#javax.persistence.Table(name = "REA_DICHIARAZIONE_INTENTO", schema = "EDIWEA")
#Entity
What is the difference between the #Table(name = "REA_DICHIARAZIONE_INTENTO") annotation and the #javax.persistence.Table(name = "REA_DICHIARAZIONE_INTENTO", schema = "EDIWEA") annotation (used in my project)?
2) The second doubt is related to this annotation:
#javax.persistence.IdClass(it.enel.wearea.entity.ReaDichiarazioneIntentoPK.class)
What exactly means?
3) The last doubt is related to the mapping between a class field to a table column on the DB. Why is it done only on the getter method and not directly on the field name?
Tnx
It is using JPA annotations, and Hibernate is a JPA implementation. JPA by itself is just a set of interfaces/annotations, while JPA implementation (like Hibernate) provides meat around those interfaces/annotations. There is no difference between the two annotations, other than specified schema. Hibernate also has its own #Table annotation but it is used for additional information supplied by JPA'a #Table annotation
#IdClass means that the complex primary key is used for this entity
Specifies a composite primary key class that is mapped to multiple fields or properties of the entity.
You can annotate fields or properties (getters), it's up to you. But, #Id mapping dictates what is valid, meaning if you put #Id on field then you must put all other mappings on fields also, and vice versa.
This is using JPA, looks like, not hibernate. Here is the difference according to SO and here is another link

Categories