I'm implementing a custom hibernate JPA naming strategy in a Hibernate 5.4.32 model.
The implicit naming strategy is setup to convert camelCase property names to snake_case. Thus a property called myKey will turn into a column called my_key. This part works correct, and all columns have the correct names and all JPA interactions work.
However I have an issue with generating index names. In that Strategy interface there is a method:
public Identifier determineIndexName(ImplicitIndexNameSource source)
The parameter "source" passed in that method is ambiguous in how it handles the column names that are passed to the method.
For instance if I create the following entity
#Table(schema = LoaderModel.SCHEMA, name = "main_entity",indexes = {#Index(columnList = "other_id,myKey")} )
#Entity
public class MainEntity{
#ManyToOne
OtherEntity other;
#Column(columnDefinition = "text")
String myKey;
}
This entity creates a table with two columns, other_id, and my_key. But notice how in the #Index I must use the property name "myKey", even though for the ManyToOne column I have to use the actual column name: "other_id". This is in line with what I see in the determineIndexName method:
#Override
public Identifier determineIndexName(ImplicitIndexNameSource source) {
String name = "ix_"+source.getTableName().getText()+"_"+String.join("_",source.getColumnNames().stream().map(Identifier::getText).collect(Collectors.toList()));
//The name here is: ix_main_entity_other_id_myKey
//instead of the desired: ix_main_entity_other_id_my_key
return Identifier.toIdentifier(name);
}
I can't find any properties of the ImplicitIndexNameSource object that allow me to detect the actual column names of the properties in the index. In addition I'm not sure why it passes the column name for entity references, but the property name for entity properties.
I'm guessing some portion of this weird ambiguity is intended, but If I could simply get access to the real column name then I could write the code to handle it.
Note: Manually configuring the column name "my_key" for the myKey property seems to fix this issue, but that defeats the purpose of creating the naming strategy (which would be applied to the entire model), so that's not a valid fix.
Related
Due to the fact that I'm using an abstract class, I get such error:
org.springframework.data.mapping.MappingException: Ambiguous field mapping detected!
Both private final com.life.book.domain.event.EventType com.life.book.domain.command.ObjectCommand.eventType
and private final com.life.book.domain.event.EventType com.life.book.domain.command.UpdateObjectCommand.eventType
map to the same field name eventType! Disambiguate using #Field annotation!
My classes:
abstract class ObjectCommand(
open var eventType: EventType?
)
#Document(collection = "COMMAND")
data class UpdateObjectCommand(
val description: String?,
override var eventType: EventType?
) : ObjectCommand(eventType)
enum class EventType {
CREATED, UPDATED
}
The solution might be to use a different name instead of the eventType name in the UpdateObjectCommand class. But then the database will have two fields with the same purpose. Maybe there is another way?
There is Disambiguate using #Field annotation in the description of the error, but I don't understand how to use it.
It's been a sec since I have delt with what looks like Mongo DB and Spring Boot (I could be off base here). The #Field annotation is applied to fields in your class that have the #Document annotation.
You can add information to the #Field annotation like the serialized name of the field, or if its written when null. #Field in most cases does not need to be applied to a field unless you are dealing with enums. Mongo does not know how to store an enum, so you must add the #Field annotation to specify that it needs to be stored as a string.
https://blog.tericcabrel.com/using-mongodb-with-spring-boot-project-part-1/
From tericcabrel.com:
#Field is used to enhance the property by changing the type; like our case, MongoDB doesn't support Enum, so we need to tell Mongo to store this property's value as a string. When retrieving the data from the collection, the value will be returned back as an Enum. You can also provide a different name for the property in MongoDB.
I'd like JPA EclipseLink creates tables exactly as is the ClassName (and fields) WITHOUT annotation #Table and #Column. It's always creates tables and fields with UPPERCASE, which makes readability difficult in the DB console.
ex.:
#Entity
public class ChannelEntity {
#Id #GeneratedValue
public Long id;
public String name;
public String description;
public Boolean oficial;
#Temporal(TemporalType.TIMESTAMP)
public Date creation;
}
And I'd like results in
Table: ChannelEntity
id creation description name oficial
----------------------------------------------------
351 NULL meu desc meu nome 1
Maybe exist same parameter in persistence.xml, but I can't find it.
If EclipseLink behaves correctly, the parameter you are looking for is delimited-identifers. From the JPA 2.1 spec:
It is possible to specify that all database identifiers in use for a
persistence unit be treated as delimited identifiers by specifying the
<delimited-identifiers/> element within the persistence-unit-defaults
element of the object/relational xml mapping file. If the
<delimited-identifiers/> element is specified, it cannot be
overridden.
If this element is included, EclipseLink should delimit all database identifiers in its generated SQL, which would cause the database objects to be created with case-sensitive names.
In our product we use auto generated hibernate entities to be able to link a customizable Database scheme to our server software. The entity names and property names are taken from the data base. Especially, the property names can usually not be changed as they also are used in user code unrelated to the hibernate data layer (e.g. python scripts)
Some of these property names are capitalized, which seems to cause some problems. HQL statements using those property names fail with an Exception, e.g.:
org.hibernate.QueryException: could not resolve property List_id
at org.hibernate.QueryException.generateQueryException(QueryException.java:137)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:120)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:234)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:158)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:126)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:88)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:190)
Some code snippet for the example Exception:
#Entity(name = "ListItem")
#Table(name = "LIST_ITEM")
public class ListItem
extends HibernatePojoClass
{
private String List_id = "";
#Column(name = "`LIST_ID`", length = 8)
public String getList_id() {
return List_id;
}
public void setList_id(String List_id) {
this.List_id = List_id;
}
...
and the HQL statement:
select li.id, li.List_id from ListItem li
The exception occurs when hibernate tries to transform the hql statement to a sql statement.
Why does this happen?
It seems that when I use li.list_id in the hql statement, the property is resolved (while this leads to another error); can I prevent this implicit "capitalization change" somehow?
if you use
#Column(name = "`LIST_ID`", length = 8)
public String getList_id() {
return List_id;
}
you should refer that property as list_id in HQL, of course.
Hibernate can use a naming strategy to generate column names. ImprovedNamingStrategy from Hibernate 4 will convert column name to the lower case, even if you specify it. I am not sure about the quotes, but for this:
#Column(name = "LIST_ID", length = 8)
public String getList_id() {
return List_id;
}
using ImprovedNamingStrategy you will have list_id column name.
You can try to use your own naming strategy to generate correct column names.
JPA has 2 basic access modes: property access and field access.
Property access requires you to adhere to the Java Beans convention which means you need field name that starts with a lower case character and a corresponding getter/setter which has the same character in upper case, i.e. field listId would require a getter getListId().
Thus you'd need to use field access in order to have Hibernate use the field name as it is. Another advantage of using field access on an entity's id would be that you'd not need to do any lazy loading in order to just get the id - which wouldn't be possible with property access in Hibernate.
For more information have a look at sections 2.2 and 2.3 of the JPA specification.
A final word of advice though: as already stated multiple times in my comments you should try and stick with the Java code conventions. Some advantages of doing so:
It'll be easier to communicate with others such as people here on SO (e.g. a name starting with a capital letter normally is assumed to be a class name).
You'll have less problems with libraries in the Java eco system since most of them use the same conventions or are based on them (e.g. JavaBeans, JavaEL, etc.)
It'll be easier to spot errors, e.g. when using a class rather than a field or variable etc.
You'll be less dependent on IDE features like code coloring, error highlighting etc.
I have two classes: TranscriptionService and TranscriptionConfig.
TranscriptionConfig has a serviceName variable, that is a string, with setters and getters.
TranscriptionService has no variables or references in the class to TranscriptionConfig.
There are two database tables: transcriptionConfig and transcriptionServices. transcriptionConfig has a foreign key between its field serviceName, and name inside transcriptionServices.
An admin should be able to set the string value of serviceName inside transcriptionConfig. This then references the equivalent string inside name in transcriptionServices. The transcriptionServices entries in the database are pre defined manually, so they never need to be set using an object.
My current hibernate code inside transcriptionConfig for the serviceName is as follows:
#ManyToOne
#JoinColumn(name = "serviceName", nullable = false)
private String transcriptionService;
However, it will not allow me to do this, as a String is not an entity. I have tried adding target-entity to no avail.
It seems to have a manyToOne relationship, it would need to have an instance of the TranscriptionService class, but I do not want transcriptionConfig to contain this object. It just needs a reference with the names.
How I can use this ManyToOne relationship, but just pass around the string for the name?
If you treat this column as a string value, you don't need to define mapping #ManyToOne and use #JoinColumn. Just mark it by#Column.
You'll need probably to catch SQLException in your DAO to handle foreign key constraint.
Edit:
You get this exception, because you don't have this key in foreign table. First you need to create row in TranscriptionService table.
I have an application using Hibernate for data persistence, with Spring on top (for good measure). Until recently, there was one persistent class in the application, A:
#Entity
public class A {
#Id
#Column(unique = true, nullable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
public String name;
}
I have since added a subclass of A, called B:
#Entity
public class B extends A {
public String description;
}
After adding B, I could now not load A's. The following exception was thrown:
class org.springframework.orm.hibernate3.HibernateObjectRetrievalFailureException :: Object with id: 1 was not of the specified subclass: A (Discriminator: null); nested exception is org.hibernate.WrongClassException: Object with id: 1 was not of the specified subclass: A (Discriminator: null)
I added the following annotation and property to B, and it seems to have solved the problem. Is this the right way to solve the issue?
...
#DiscriminatorFormula("(CASE WHEN dtype IS NULL THEN 'A' ELSE dtype END)")
public class A {
private String dtype = this.getClass().getSimpleName();
...
(...) Until recently, there was one persistent class in the application, A:
With the following database representation:
ID NAME
-- ----
1 foo
2 bar
I have since added a subclass of A, called B (...)
And you didn't specify the Inheritance annotation so the SINGLE_TABLE mapping strategy is used. And In this strategy, all the classes in a hierarchy are mapped to a single table. The table has a column that serves as a “discriminator column”, that is, a column whose value identifies the specific subclass to which the instance that is represented by the row belongs.
The table then became:
ID NAME DTYPE
-- ---- -----
1 foo NULL
2 bar NULL
Where DTYPE is the default name of column to be used for the discriminator.
After adding B, I could now not load A's. The following exception was thrown (...)
Indeed, because existing values have a null value in the discriminator column, the provider doesn't know what subclass to instantiate.
I added the following annotation and property to B, and it seems to have solved the problem. Is this the right way to solve the issue?
That's one way but it is intrusive (your entities shouldn't be aware of the dtype column) and Hibernate specific. In other words, it's a hack.
For me, the "right" way to solve this would be to update the DTYPE column of existing A records to set the value to 'A' (with Hibernate, the value defaults to the entity name):
UPDATE A SET DTYPE='A' WHERE DTYPE=NULL
This way, Hibernate would be able to load them properly.