Spring Boot Hibernate 5 Ignoring #Table and #Column - java

This is driving me mad.
I'm implementing Spring Social and it requires you to have a database table named UserConnection (instead of using the standard naming convention of using an underscore to separate the two words).
So in my naive world view, I assumed it would be easily solved by specifying #Table(name="UserConnection")... but no, that would be all too easy.
The annotation is ignored and the table is created as user_connection which then causes Spring Social to have a hissy fit.
Please tell me there's some easy way to tell my Spring Boot app to just name that one table (and its corresponding columns) to use a camel-case naming convention instead of the standard one.

TL; DR
Add the following to your application.yml file:
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Or your application.properties:
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
Detailed Answer
As Spring Boot 1.4 release notes states:
SpringNamingStrategy is no longer used as Hibernate 5.1 has removed
support for the old NamingStrategy interface. A new
SpringPhysicalNamingStrategy is now auto-configured which is used in
combination with Hibernate’s default ImplicitNamingStrategy. This
should be very close to (if not identical) to Spring Boot 1.3
defaults, however, you should check your Database schema is correct
when upgrading.
This new PhysicalNamingStrategy follows Spring recommended naming conventions. Anyway if you want total control over physical naming, you're better off using the org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl. You can switch to that naming strategy by adding the following to your application.yml:
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
The annotation is ignored and the table is created as user_connection
which then causes Spring Social to have a hissy fit.
The apply method of SpringPhysicalNamingStrategy is the key to understand this behavior:
private Identifier apply(Identifier name, JdbcEnvironment jdbcEnvironment) {
if (name == null) {
return null;
}
StringBuilder builder = new StringBuilder(name.getText().replace('.', '_'));
for (int i = 1; i < builder.length() - 1; i++) {
if (isUnderscoreRequired(builder.charAt(i - 1), builder.charAt(i),
builder.charAt(i + 1))) {
builder.insert(i++, '_');
}
}
return getIdentifier(builder.toString(), name.isQuoted(), jdbcEnvironment);
}
private boolean isUnderscoreRequired(char before, char current, char after) {
return Character.isLowerCase(before) && Character.isUpperCase(current)
&& Character.isLowerCase(after);
}
It basically replaces any . and case changes (take a look at isUnderscoreRequired method) with an underscore.

Option 1
First of all define your tables name on the #Entity mapping:
#Entity( name = "UserConnections")
public class UserConnection{
Option 2
You should pay a bit with the NamingStrategy. When you define your properties for the sessionFactory bean then try adding this:
<prop key="hibernate.implicit_naming_strategy">legacy-jpa</prop>
When an entity does not explicitly name the database table that it
maps to, we need to implicitly determine that table name. Or when a
particular attribute does not explicitly name the database column that
it maps to, we need to implicitly determine that column name.
So if you do not want to explicitly name your table names for each of the entities you should follow this strategy.
Option 3
Alternatively if the above do not work for you, you have to use the PhysicalNamingStrategy. Though this is the last resort in your case:
REference: https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/naming.html

Related

Spring Boot + JPA : JPA seems to auto create tables but not sure where

I am practicing/learning some Spring Boot + JPA application and keep running into issues when attempting to save to database table.
It seems JPA is auto generating table name, even though I have provided the #Table annotation.
I have a simple POJO , marked with #Entity
#Entity
#Table(name="SongList")
public class SongList {
#Id
private Integer id;
private String album;
private String artist;
private String title;
//Getter and Setter Methods
}
An Interface that extends JPA CrudRepository
import org.springframework.data.repository.CrudRepository;
public interface songRepo extends CrudRepository<SongList,Integer> {
}
A properties yml file that sets datasource properties
spring:
datasource:
driverClassName : com.mysql.cj.jdbc.Driver
url : jdbc:mysql://localhost:3306/Songdb
username : root
password : learning
jpa:
hibernate.ddl-auto : update
generate-ddl : false
show-sql : true
And finally Test class :
#SpringBootTest
#RunWith(SpringRunner.class)
class DataJpaApplicationTests {
#Autowired
ApplicationContext context;
#Test
void saveSongs() {
songRepo repo = (songRepo) context.getBean(songRepo.class);
SongList songs = new SongList();
songs.setId(4);
songs.setTitle("High Hopes");
songs.setAlbum("Panic! At the Disco");
repo.save(songs);
}
}
Upon running the Test class , my test fails with error :
ERROR:
org.hibernate.exception.SQLGrammarException: could not extract ResultSet
Caused by: java.sql.SQLSyntaxErrorException: Table 'songdb.song_list' doesn't exist
My database table already exists. The database is called Songdb (not songdb) and table is SongList.
I am not sure where is the table name 'song_list' being injected and why my database name is 'songdb' instead of 'Songdb' as provided in the url.
What am I missing here ? Please help!
That is because you are using the default naming strategy provided by Spring boot. Hibernate maps field names using a physical strategy and an implicit strategy. Hibernate uses the Physical Naming Strategy to map our logical names to a SQL table and its columns. Spring Boot, provides defaults for both these strategies spring.jpa.hibernate.naming.physical-strategy defaults to org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy, and
spring.jpa.hibernate.naming.implicit-strategy defaults to org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
The default naming strategy for spring-boot will :
Replace dots with underscores
Change camel case to snake case
Lower-case table name
You can change it by setting the property like :
spring:
jpa:
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
For you just changing the physical naming strategy will fix your issue. You could also use org.hibernate.cfg.EJB3NamingStrategy which will keep the table name as it is provided. Now that you know the cause for the issue , you can fix it ay way you like.
You could customize the physical naming strategy like you want incase you want more control. Read : Hibernate naming strategy changing table names

Hibernate throwing validation exception "wrong column type encountered in column" even when my DDL script and JPA entity are in sync

I am starting my spring container in validate mode
autoddl=validate
I am getting a validation exception like this
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-
validation: wrong column type encountered in column [amount] in table [Balance];
found [numeric (Types#NUMERIC)], but expecting [int8 (Types#BIGINT)]
and my DDL script goes like this
CREATE TABLE Balance(stratr VARCHAR(25), histFromDate TIMESTAMP WITHOUT TIME
ZONE,amount numeric(11, 0))
and my attribute in JPA entity goes like this
#Column(name="amount", precision=11, scale=0) //have specified precision and scale
private Long amount ;
where I have used import javax.persistence.Column.Since I have annotated the exact precision and scale, Shouldn't hibernate validate with these info that I have provided through the column annotation? What could have I missed ?
I cannot do the following
#Column(
columnDefinition = "NUMERIC(11,0)"
)
private Long amount;
because I don't know the data store of this JPA entity.
I also tried generating the script by the following property
<prop key="javax.persistence.schema-generation.scripts.action">drop-and-create</prop>
<prop key="javax.persistence.schema-generation.scripts.create-target">./l/create.sql</prop>
<prop key="javax.persistence.schema-generation.scripts.drop-target">./l/drop.sql</prop>
This is also generating as int8 and not numeric(11,0). What can be done to solve this ?
It's really quite difficult to grasp what you're trying to accomplish, but if I understood correctly:
you want to keep your application portable by not fixing the column definition on the entity level to be NUMERIC(11,0), which would make it Postgres-specific
at the same time, you want your column to use NUMERIC(11,0) for Postgres and not INT8 that Hibernate would normally use for a Long in Postgres (and is hoping to find in your schema upon validation)
In short, you want a per-database customization that is not reflected in your entity mapping. The only way to accomplish that is to customize the dialect that Hibernate is using for your version of Postgres. What you need to do is:
determine which dialect version is being selected for your Postgres database (it will be one of the following: PostgresPlusDialect, PostgreSQL81Dialect, PostgreSQL82Dialect, PostgreSQL91Dialect, PostgreSQL92Dialect,PostgreSQL93Dialect, PostgreSQL94Dialect, PostgreSQL95Dialect, PostgreSQL9Dialect)
extend from that class, adding the following definition:
public MyCustomPostgresDialect() {
super();
registerColumnType(Types.BIGINT, "NUMERIC(11, 0)");
}
(If you want to be able to control the precision and scale using #Column(precision = ..., scale = ...), use registerColumnType(Types.BIGINT, "NUMERIC($p, $s)") instead)
add the hibernate.dialect property to persistence.xml, pointing to the fully qualified class name of your custom dialect
Note that this will, of course, affect all Long properties in your data model, not just the specific field in question.
I can think on only reason is because in your entity amount type is Long but in JPA creation script your DDL specified as amount numeric(11, 0) here second param suggest decimal precision.
As you can see java tries to enter data in Long type (ie. 10.0000), similar to BigInt in Database but database does not accept such decimal value being type numeric (11,0)
You should be able to resolve it by either changing your java code to have entity amount type int or change DDL to have scaleInt. ie. NUMERIC(11,5).
However best bet would be to have DECIMAL type for any non Integer type.
http://www.h2database.com/html/datatypes.html#decimal_type

Hibernate Search: how to configure index for JPA entity dynamically?

I have two applications which use the same Elasticsearch instance as a search engine. Both applications share the same code base and have only minor differences.
Applications run against different databases, and hence, the different ES indices should be used.
I try to parameterize index name using SpEL like this:
#Indexed(index="${es.index.users}")
public UserEntity {}
However, it doesn't work.
The second option I've tried was setting a different prefix for different applications via hibernate.search.default.indexBase=<app_name>. However, it works only for the Lucene engine but not for ES.
Is there a way to pass the index name into #Indexed annotation on the runtime?
If not, is there another way to pass the index which should be used?
At the moment, the only solution would be to use the programmatic mapping API. This will allow you to execute code to set the index names. If you need to retrieve the index names from configuration files, that will be on you...
First, remove the #Indexed annotations from your indexed entities.
Then, implement a mapping factory:
package com.myCompany;
// ... imports ...
public class MyAppSearchMappingFactory {
#Factory
public SearchMapping getSearchMapping() {
SearchMapping mapping = new SearchMapping();
for ( Map.Entry<Class<?>, String> entry : getIndexNames() ) {
mapping.entity( entry.getKey() ).indexed().indexName( entry.getValue() );
}
return mapping;
}
private Map<Class<?>, String> getIndexNames() {
// Fetch the index names somehow. Maybe just use a different implementation of this class in each application?
}
}
Then reference it in the Hibernate ORM properties (persistence.xml, hibernate.properties, or some framework-specific file, depending on what you use):
hibernate.search.model_mapping com.myCompany.MyAppSearchMappingFactory;
And you should be all set.

HibernateException: Missing column: Wrong name

In my Java code, I have a field named isNegative with a similar column existing in database. But Hibernate insists the name should be is_negative, even with forcing the name with #Column.
#Column(name="isNegative")
private boolean isNegative;
Error:
Caused by: org.hibernate.HibernateException: Missing column:
is_negative in datasource.item
Application.properties:
#JPA
spring.data.jpa.repositories.enabled=false
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.generate-ddl=true
spring.jpa.open-in-view=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.properties.hibernate.event.merge.entity_copy_observer=allow
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
That's due to your configuration, because you are setting spring.jpa.hibernate.naming.physical-strategy to PhysicalNamingStrategyStandardImpl which will use underscores for the names.
If you check the Configure Hibernate Naming Strategy section of Spring Docs, you can see that:
Hibernate uses two different naming strategies to map names from the object model to the corresponding database names. The fully qualified class name of the physical and the implicit strategy implementations can be configured by setting the spring.jpa.hibernate.naming.physical-strategy and spring.jpa.hibernate.naming.implicit-strategy properties, respectively. Alternatively, if ImplicitNamingStrategy or PhysicalNamingStrategy beans are available in the application context, Hibernate will be automatically configured to use them.
By default, Spring Boot configures the physical naming strategy with
SpringPhysicalNamingStrategy. This implementation provides the same
table structure as Hibernate 4: all dots are replaced by underscores
and camel casing is replaced by underscores as well. By default, all
table names are generated in lower case, but it is possible to
override that flag if your schema requires it.
To solve that you need to remove this property and use the default naming strategy instead:
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
You would need spring.jpa.hibernate.naming.physical-strategy and spring.jpa.hibernate.naming.implicit-strategy
Adding following
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
to application.properties could help. This solution would work from hibernate 5.
Hope it helps.
Please find below my analysis:
If you don't want your naming strategy to add an underscore to the column name or class name, then the strategy that you need to use would look like: spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl. The things that you provide in annotations #Table and #Column’s name attribute would remain as it is. E.g. firstName attribute in entity will get a column name as firstName i.e. No change.
If you don't want to provide annotations and want to manually handle the table name and column names, you should extend the class org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl and override the required methods. If you still use annotations for some of the cases here, remember the overridden methods will apply on the names written in those annotations. spring.jpa.hibernate.naming.physical-strategy=example.CustomStrategy

How can I use capitalized property in a hibernate entity

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.

Categories

Resources