I have an abstract superclass which every Entity on my domain is a subclass of it.
Using DB schema generation, I want to create an Index for each Entity, on a field on the superclass, and without using the Table annotation on every subclass.
My superclass
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = SEQUENCE)
private Long surrogateId;
#Index(name="id_index") // Every subclass should inherit this index, with its own name
#Column(unique = true, updatable = false, nullable = false)
private UUID id = UUID.randomUUID();
An example of subclass
#Entity
public class Customer extends BaseEntity {
...
}
I tried so far:
use the Table annotation with #Index on the superclass, but Hibernate
doesn't seem to use that annotation if it is not marked with #Entity.
For example
#Table(indexes = {#Index(name="index_id", columnList = "id")})
No SQL statements are generated.
use the deprecated #Index annotation with a name "id_index", but only one index
is created on startup (the db raises an error that this index already
exists for other entities). Some generated SQL statements:
Hibernate: create index id_index on "customer" ("id")
Hibernate: create index id_index on "user" ("id")
2020-02-15 17:47:26,620 WARN o.hibernate.tool.schema.internal.ExceptionHandlerLoggedImpl - GenerationTarget encountered exception accepting command : Error executing DDL "create index id_index on "customer" ("id")" via JDBC Statement
org.postgresql.util.PSQLException: ERROR: relation "id_index" already exists
Any ideas on how to do this without too much code duplication?
Thanks
The only way I can see to do this - and it appears more trouble than it is worth to simply avoid placing an #Table annotation on each Entity - is to create a custom dialect and override the getIndexExporter() method:
public class MyPostgreSQLDialect extends PostgreSQLXXDialect{
#Override
public Exporter<Index> getIndexExporter() {
return new MyIndexExporter(this);
}
}
to return a customized Exporter, most likely extending org.hibernate.tool.schema.internal.StandardIndexExporter
public class MyIndexExporter extends StandardIndexExporter{
public MyIndexExporter(Dialect dialect){
super(dialect);
}
#Override
public String[] getSqlCreateStrings(Index index, Metadata metadata) {
//looks like you'd need to paste the whole code from superclass method
//and alter the index name accordingly
indexNameForCreation = index.getTable().getQualifiedTableName() +
"_" + index.getName();
//in the default implementation it is simply index.getName()
}
}
Alan Hay answer may work but it seems a bit of overengineering... So I decided to not use schema generation and use Liquibase instead, so I can have more control over the database.
Anyway, If anyone is having the same problem with schema generation, I tried with #Index (using eclipselink) and it worked, so this issue is only in Hibernate.
Related
I have two classes TableNameA and TableNameB inside two different dependencies DependencyA and DependencyB representing tables table_name_a and table_name_b with fields described below.
TableName: table_name_a
Field's Name: field_name_p, field_name_q, field.
TableName: table_name_b
Field's Name: field_name_r, field_name_s.
#Entity
#Table(name = "table_name_a")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class TableNameA{
#Id
private int field;
private int fieldNameP;
private int fieldNameQ;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public class TableNameB{
#Column(name = "field_name_r")
private int fieldNameR;
#Column(name = "field_name_s")
private int fieldNameS;
}
log.info(dslContext.selectFrom(TableNameA.TABLE_NAME_A)
.limit(4)
.fetch()
.into(dependencyA.TableNameA.class).toString());
log.info(dslContext.selectFrom(TableNameB.TABLE_NAME_B)
.limit(4)
.fetch()
.into(dependencyB.TableNameB.class).toString());
I am using jooq as explained above and I want to map table_name_a and table_name_b record into TableNameA and TableNameB class but in the object of TableNameA only 'field' member variable is mapped properly and rest of member variable's fieldNameP, fieldNameP are mapped to null rather than corresponding values in column of table and TableNameB is mapped properly.
The issue here is member variable's fieldNameP, fieldNameP are mapped to null rather than corresponding values in column of table
And One more condition i can't edit TableNameA and TableNameB classes instead I have to write my own models to map if i don't get solution for this.
What you describe is a known issue in jOOQ: https://github.com/jOOQ/jOOQ/issues/4586. Also the fields without a #Column annotation should be mapped, unless they are annotated as #Transient. The reason field gets mapped properly is that it has an #Id annotation.
For the time being I suggest you vote for the linked GitHub issue so that the issue gets attention and can be properly prioritized.
I'm using Hibernate with JPA to connect to a MySql database. The database is created before the application launch so Hibernate is not building the database for me. This application is a webapp that is to be deployed to tomcat.
For one table I am using some Generics to handle some values that may either be String, Integer or Boolean. The table is created with this statement:
CREATE TABLE IF NOT EXISTS `registry` (
`DTYPE` varchar(31) NOT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`configName` varchar(255) NOT NULL,
`label` varchar(255) NOT NULL,
`configValue` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET=latin1;
The classes it is handling are an abstract class base with three classes that extends the abstract. Like this:
#Entity
#Table(name = "registry")
public abstract class Config<T> implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String configName;
private String label;
// standard getters and setters for the above fields.
public abstract void setConfigValue(T value);
public abstract T getConfigValue();
}
#Entity
public class BooleanConfig extends Config<Boolean>{
private Boolean configValue;
public void setConfigValue(Boolean configValue){
this.configValue = configValue;
}
public Boolean getConfigValue(){
return this.configValue;
}
}
#Entity
public StringConfig extends Config<String>{
private String configValue;
public void setConfigValue(String configValue){
this.configValue = configValue;
}
public String getConfigValue(){
return this.configValue;
}
}
#Entity
public IntegerConfig extends Config<Integer>{
// and similar as the other two but with Integer.
}
All these classes are listed in my persistence.xml and when I run the application from Eclipse for debugging, everything works as expected and the values are written and edited as I would expect them to be. My problem is once I compile the war file and upload it to Tomcat. When the webapp is deployed to Tomcat there is an error during the start up of the application caused by the database.
SchemaManagementException: Schema-validation: wrong column type encountered in column [configValue] in table [registry]; found [varchar (Types#VARCHAR)], but expecting [bit (Types#BOOLEAN)]
Now I figure the answer to the problem is that I will need to go into each of the extending classes and map the configValue column to a specific data type. My question is why do I not receive the exception when running from my IDE?
Ok I apparently fixed this problem and totally forgot that I had posted this question. So here's what I was doing wrong. The problem basically comes from the handling of the configValue of the extending classes. Each one here used its own type but the database requires that the type is varchar(255). Once I provided a Column Definition for the configValue in the form of:
#Column(columnDefinition = 'varchar(255)')
private Integer configValue; // this could be for all the types listed in the question.
the problem solved itself. As for the reason it didn't show itself while debugging in Eclipse, I'm still not sure, but this fix caused no problems during debugging and solved the problem during deployment. I didn't figure this all out myself, but I can't remember who helped me.
There is the following case:
#javax.persistence.Entity
#javax.persistence.Table(name = "another_table")
public class Table {
...
#javax.persistence.ManyToMany
public java.util.Set<sth> getSth() {
return this.sth;
}
While making the query JPA thinks correcly that there is many to many table "another_table_sth" but expects to find column "table_id" in it as defined by the class name. However, there is actually a column another_table_id as defined by the #javax.persistence.Table annotation.
How to define this case in the naming strategy?
I have a Client and Affiliate class, inheriting from Person class. Joined inheritance strategy type is being used - each of them sharing primary key with the parent class. As there's no discriminator column we chose to use DescriptorCustomizer and ClassExtractor. But it doesn't really give any idea how it works, also, the code doesnt seem to compile. It would be nice if someone gives a nice example with code snippet for understanding.
According to the mentioned documentation:
If you are mapping to an existing database, and the tables do not have
a discriminator column you can still define inheritance using the
#ClassExtractor annotation or <class-extractor> element. The class
extractor takes a class that implements the ClassExtractor
interface. An instance of this class is used to determine the class
type to use for a database row. The class extractor must define a
extractClassFromRow() method that takes the database Record and
Session.
we need to annotate the root entity in a hierarchy with user defined using the class extractor:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#ClassExtractor(PersonClassExtractor.class)
public abstract class Person {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int age;
// ...
}
Notice that we don't use #Customizer annotations since as this is not required in case of JOINED inheritance strategy:
If a class extractor is used with SINGLE_TABLE inheritance, the rows
of the class type must be able to be filtered in queries. This can be
accomplished by setting an onlyInstancesExpression() or
withAllSubclassesExpression() for branch classes. These can be set
to Expression objects using a DescriptorCustomizer.
The class extractor must be able to determine and return the class type from the database row.
In general we need a replacement of a discriminator column, i.e.
column name unique for a given entity type among others
criteria based on values of a given column of the root entity
Suppose that each of inherited entity type in a hierarchy has a column with unique name:
#Entity
public class Client extends Person {
#Column(name = "CLIENT_SPECIFIC")
private String clientSpecific;
// ...
}
#Entity
public class Affiliate extends Person {
#Column(name = "AFFILIATE_SPECIFIC")
private float affiliateSpecific;
// ...
}
then class extractor may look as follows:
public class PersonClassExtractor extends ClassExtractor {
#Override
public Class<?> extractClassFromRow(Record databaseRow, Session session) {
if (databaseRow.containsKey("CLIENT_SPECIFIC")) {
return Client.class;
} else if (databaseRow.containsKey("AFFILIATE_SPECIFIC")) {
return Affiliate.class;
} else {
return Person.class; // this should never happen
}
}
}
retrieve a list of clients and affiliates
List<Person> polymorphicResults = em.createQuery("SELECT p FROM Person p")
.getResultList();
retrieve a list of affiliates or clients respectively
List<Affiliate> concreteResults = em.createQuery("SELECT a FROM Affiliate a")
.getResultList();
List<Client> concreteResults = em.createQuery("SELECT c FROM Client c")
.getResultList();
We use annotations for mapping the entity class with the database table by simply specifying #Entity and more like #Id, table joins and many things. I do not know how these entity variables are getting mapped with database table. Can anyone give a short description for understanding.
Thanks :)
Well the idea is to translate your objects and their connections with other objects into a relational database. These two ways of representing data (objects defined by classes and in tables in a database) are not directly compatible and that is where a so called Object Relational Mapper framework comes into play.
So a class like
class MyObject
{
private String name;
private int age;
private String password;
// Getters and setters
}
Will translate into a database table containing a column name which is of type varchar, age of type int and password of type varchar.
Annotations in Java simply add additional information (so called meta data) to your class definitions, which can be read by any other class (e.g. JavaDoc) and in the case of the Java Persistence API will be used by an ORM framework like Hibernate to read additional information you need to translate your object into the database (your database table needs a primary id and some information - like what type of a relation an object has to another - can't be automatically determined by just looking at your class definition).
Annotations are very well explained here:
http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/
annotations are just metadata on a class, nothing magical. You can write your own annotations. Those annotations are given retention policies of runtime (which means you have access to that metadata at runtime). When you call persist etc the persistence provider iterates through the fields (java.lang.reflect.Field) in your class and checks what annotations are present to build up your SQL statement. Try writing your own annotation and doing something with it. It won't seem very magical after that.
in your case annotation working means mapping with tablename with entity class is look like as ....
#Entity
#Table(name = "CompanyUser")
public class CompanyUserCAB implements java.io.Serializable
{
private long companyUserID;
private int companyID;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "companyUserID")
public long getCompanyUserID()
{
return this.companyUserID;
}
public void setCompanyUserID(long companyUserID)
{
this.companyUserID = companyUserID;
}
#Column(name = "companyID")
public int getCompanyID()
{
return this.companyID;
}
public void setCompanyID(int companyID)
{
this.companyID = companyID;
}
}