Is there a way to specify distinct sequences for each table in Hibernate, if the ID is defined on a mapped superclass?
All entities in our application extend a superclass called DataObject like this:
#MappedSuperclass
public abstract class DataObject implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
private int id;
}
#Entity
#Table(name = "entity_a")
public class EntityA extends DataObject { ... }
#Entity
#Table(name = "entity_b")
public class EntityB extends DataObject { ... }
This causes all entities to use a shared sequence, the default hibernate_sequence.
What I would like to do is use a separate sequence for each entity, for example entity_a_sequence and entity_b_sequence in the example above. If the ID were specified on the subclasses then I could use the #SequenceGenerator annotation to specify a sequence for each entity, but in this case the ID is on the superclass. Given that ID is in the superclass, is there a way I can use a separate sequence for each entity — and if so, how?
(We are using PostgreSQL 8.3, in case that's relevant)
Have you tried doing it this way ?
#MappedSuperclass
public abstract class DataObject implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgen")
#Column(name = "id")
private int id;
}
#Entity
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "entityaseq")
#Table(name = "entity_a")
public class EntityA extends DataObject {
}
#Entity
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "entitybseq")
#Table(name = "entity_b")
public class EntityB extends DataObject {
}
I'm sorry I don't have the required environment to test it right now but I'll try it later.
We use this in the abstract superclass of all of our JPA entities:
#Id
#GeneratedValue(generator = "pooled")
#GenericGenerator(name = "pooled", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = {
#org.hibernate.annotations.Parameter(name = "value_column_name", value = "sequence_next_hi_value"),
#org.hibernate.annotations.Parameter(name = "prefer_entity_table_as_segment_value", value = "true"),
#org.hibernate.annotations.Parameter(name = "optimizer", value = "pooled-lo"),
#org.hibernate.annotations.Parameter(name = "increment_size", value = "100")})
private Long id;
It's a bit verbose, but it allows setting the prefer_entity_table_as_segment_value which means you don't need to repeat the id field or the generator annotations in the subclasses.
I was not quite happy about the need to declare the sequence name on each class individually. I have checked the source code and came up with this solution:
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.id.enhanced.SequenceStyleGenerator;
// ...
#Id
#GeneratedValue(generator = "sequenceIdGenerator")
#GenericGenerator(
name = "sequenceIdGenerator",
strategy = "sequence",
parameters = #Parameter(
name = SequenceStyleGenerator.CONFIG_PREFER_SEQUENCE_PER_ENTITY,
value = "true"))
#Column(updatable = false, nullable = false)
protected Long id;
IHMO there is better way to do this:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
It works in my app.
TABLE generation stretergy uses separate db sequence for each table but it is little expensive operation
Related
I was following MappedSuperclass - Change SequenceGenerator in Subclass to move id field up to the AbstractEntity from all the child entity classes but also keeping their distinct sequences.
All entities extend a superclass called AbstractEntity like this:
#MappedSuperclass
#EntityListeners(RecordAuditorListener.class)
public class AbstractEntity implements Serializable {
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_GEN")
protected Integer id;
...
The child entity named Product:
#Entity
#SequenceGenerator(name = "ID_GEN", sequenceName = "SQ_PRODUCT", allocationSize = 1)
#Table(name = "PRODUCT")
#EntityListeners({OnProductChangeSyncRequestCreatorListener.class})
#XmlRootElement
public class Product extends AbstractEntity implements Attributable {
//This id field was removed from here
// #Id
// #Column(name = "ID", nullable = false)
// #GeneratedValue(generator = "SQ_PRODUCT")
// #SequenceGenerator(name = "SQ_PRODUCT", sequenceName = "SQ_PRODUCT", allocationSize = 1)
// private Integer id;
...
The child entity named Partner:
#Entity
#SequenceGenerator(name = "ID_GEN", sequenceName = "SQ_PARTNER", allocationSize = 1)
#Table(name = "PARTNER")
#EntityListeners({OnProductChangeSyncRequestCreatorListener.class})
#XmlRootElement
public class Partner extends AbstractEntity implements Attributable {
//This id field was removed from here
// #Id
// #Column(name = "ID", nullable = false)
// #GeneratedValue(generator = "SQ_PARTNER")
// #SequenceGenerator(name = "SQ_PARTNER", sequenceName = "SQ_PARTNER", allocationSize = 1)
// private Integer id;
...
When running the application I get this error message.
Exception Description: Conflicting annotations with the same name [ID_GENERATOR] were found. The first one [#javax.persistence.SequenceGenerator({allocationSize=1, name=ID_GENERATOR, sequenceName=sq_PARTNER})] was found within [...] and the second [#javax.persistence.SequenceGenerator({allocationSize=1, name=ID_GENERATOR, sequenceName=sq_PR_PRODUCT})] was found within [...]. Named annotations must be unique across the persistence unit..
In the related question it says that this is an incorrect solution in eclipselink 2.6.2. I am using eclipselink 2.6.9. is there a way that it can be done?
We create more than 100 tables in a schema in our application we used GenerationType.IDENTITY but this strategy does not support batching insert so we wanna switch to GenerationType.SEQUENCE.
#MappedSuperclass
public abstract class DataObject implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgen")
#Column(name = "id")
private int id;
}
#Entity
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "entityaseq")
#Table(name = "entity_a")
public class EntityA extends DataObject {
}
#Entity
#SequenceGenerator(initialValue = 1, name = "idgen", sequenceName = "entitybseq")
#Table(name = "entity_b")
public class EntityB extends DataObject {
}
Reference:
https://hibernate.atlassian.net/browse/HHH-12329?page=com.atlassian.jira.plugin.system.issuetabpanels%3Aall-tabpanel
But this creates one table sequence for each table entity and this is very over.
Can there be any workaround to make a shared table for all entities?
Shared sequence for all Entities that extend DataObject:
First define the sequence in the MappedSuperclass:
#MappedSuperclass
public abstract class DataObject implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="idgen")
#SequenceGenerator(name="idgen", sequenceName="entity_seq", allocationSize=1)
#Column(name = "id")
private int id;
}
Then remove any annotations about sequence from your Entities:
#Entity
#Table(name = "entity_a")
public class EntityA extends DataObject {
}
#Entity
#Table(name = "entity_b")
public class EntityB extends DataObject {
}
This way suppose you save EntityA and gets id 1, a subsequent save to EntityB will use the same sequence and will get id 2.
Currently I define the tables the following way:
The example is in Kotlin, but you can answer in Java if you want.
#Entity
data class Task(#Id #GeneratedValue(strategy = GenerationType.SEQUENCE)
var id: Int = 0,
#ManyToOne(optional = false) #OnDelete(action = OnDeleteAction.CASCADE)
var exercise: Exercise = Exercise(),
#Column(nullable = false)
var name: String = "")
#Entity
data class Exercise(#Id #GeneratedValue(strategy = GenerationType.SEQUENCE)
var id: Int = 0,
#Column(nullable = false)
var count: Int = 0)
Using this example all tables are using the same sequence: hibernate_sequence.
If I want to configure it e.g. setting a custom allocationSize, then I must define it in every table, am I right?
#SequenceGenerator(name = "task_seq", sequenceName = "task_seq", allocationSize = 100)
Is there a Bean or anything else? Because I like the idea of using the same sequence for all tables.
You can have an abstract base class with Id like :
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
#MappedSuperclass
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
and every entity will extend this.
I have an abstract base entity class with an id property.
I would like to have specific annotations applied to this property if the current database is H2:
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = id_generator")
#Column(name = "id")
private Long id;
And some other specific anotations applied to this property if the current database is MySQL:
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
#Column(name = "id")
private Long id;
The base class:
#MappedSuperclass
public abstract class AbstractEntity {
All entity classes extend the base class like:
#Entity
#Table(name = "user_account")
#SequenceGenerator(name = "id_generator", sequenceName = "sq_id_user")
public class User extends AbstractEntity {
Maybe I could leverage some conditional custom annotations ?
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Conditional(DbH2Condition.class)
public #interface DbH2 {
}
public class DbH2Condition implements Condition {
private static final String DB = "h2";
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("db") == null
|| context.getEnvironment().getProperty("db").equals(DB);
}
}
you can add annotations using external byte code manipulation framework, such as JavaAssist, during compile time. But it means that you need to know in compile time of course, which database you will use.
Given a table (MY_TABLE_A) that automatically increments it's id upon each new insertion (i.e. the first record in the database has it's ID attribute 1, the second record has it's ID attribute set to 2, the third record has it's ID attribute set to 3). The ID I am talking about is the table's primary key.
I also have another table (MY_TABLE_B) that reference's the original table's primary key. When I try to persist both to my Oracle database, I get a org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save()
What I want to accomplish: Whenever I persist an object to MY_TABLE_A, I want MY_TABLE_B to insert an object with the same ID that MY_TABLE_A gets since it's auto incremented (wouldn't know what the next value is until it's inserted). To clarify, one id in Table A should have only one matching ID in Table B
Here are some snippets of my code below:
FirstClass:
#Entity
#Table(name = "MY_SCHEMA.MY_TABLE_A")
#Component
public class FirstClass implements Serializable {
#Id
#SequenceGenerator(name = "MY_SEQ", sequenceName = "MY_SCHEMA.MY_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_SEQ")
#Column(name = "MY_ID")
private Integer myId;
// more variables, getters/setters
}
SecondClass:
#Entity
#Table(name = "MY_SCHEMA.MY_TABLE_B")
#SecondaryTable(name = "MY_SCHEMA.MY_TABLE_A", pkJoinColumns = #PrimaryKeyJoinColumn(name = "MY_ID", referencedColumnName = "MY_ID"))
#Component
public class SecondClass {
#Id
#Column(name = "MY_ID")
private Integer myId;
// more variables, getters/setters
}
Service Layer snippet where I insert new entries for each in Oracle:
firstClassService.insert();
secondClassService.insert();
Details on insert() for firstClassService:
public void insert() {
FirstClass obj = new FirstClass();
getCurrentSession().persist(obj);
}
insert() for secondClassService:
public void insert() {
SecondClass obj = new SecondClass();
getCurrentSession().persist(obj);
}
UPDATE
What FirstClass looks like now:
#Entity
#Table(name = "MY_SCHEMA.MY_TABLE_A")
#Component
public class FirstClass implements Serializable {
#Id
#SequenceGenerator(name = "MY_SEQ", sequenceName = "MY_SCHEMA.MY_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_SEQ")
#Column(name = "MY_ID")
#OneToOne(mappedBy = "myId")
private Integer myId;
}
SecondClass:
#Entity
#Table(name = "MY_SCHEMA.MY_TABLE_B")
#SecondaryTable(name = "MY_SCHEMA.MY_TABLE_B", pkJoinColumns = #PrimaryKeyJoinColumn(name = "MY_ID", referencedColumnName = "MY_ID"))
#Component
public class SecondClass implements Serializable {
#Id
#JoinColumn(name = "MY_ID", referencedColumnName = "MY_ID")
#OneToOne
private Integer restRequestId;
}
Mappings should be as below:
#Entity
#Table(name = "MY_SCHEMA.MY_TABLE_A")
#Component
public class FirstClass implements Serializable {
#Id
#SequenceGenerator(name = "MY_SEQ", sequenceName = "MY_SCHEMA.MY_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MY_SEQ")
#Column(name = "MY_ID")
private Long myId;
#OneToOne(mappedBy = "firstClass", cascade = CascadeType.ALL)
private SecondClass secondClass;
}
#Entity
#Table(name = "MY_SCHEMA.MY_TABLE_B")
#Component
public class SecondClass implements Serializable {
#Id
#JoinColumn(name = "MY_ID", referencedColumnName = "MY_ID")
#OneToOne
private FirstClass firstClass;
}
With the Cascade option set then you you will only need to make the call to save firstClass: the associated secondClass will be persisted automatically - assuming you set both sides of the relationhsip in your in-memory model i.e.
firstClass.setSecondClass(secondClass);
secondClass.setFirstClass(firstClass);
Add #GeneratedValue(strategy=GenerationType.IDENTITY) to the id of second class.
#Id
#Column(name = "MY_ID")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer myId;
// more variables, getters/setters
From your description it seems like you have a ManytoOne relation, as your table B references table A, then it's logic to say A has a list of Bs somewhat, so why not take advantage of what ORM actually is and why not keep a reference in A such as:
#OneToMany(mappedBy="aa")
private List<B> bs;
and use the annotation in the other entity:
#ManyToOne
#JoinColumn(name = "myId" , referencedColumnName = "id")
private A aa;
That in combination to what Jens suggested, see OracleDialect does not support identity key generation