JPA #MappedSuperclass different Id counters for subclasses - java

I've got a #MappedSuperclass which is my base-class for all my entities (#Entity, direct or indirect through multiple sub-classing).
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#XmlAttribute(required = true)
private Long primaryKey;
The Id is generated like shown above.
My problem is that the #Id-counter is the same for each #Entity. In fact thats not a big problem because it would take a while to reach the Long.MAX_VALUE. But reaching that maximum value is a lot easier because there is only one counter for all entities. How can I use a different #Id-counter without having to add the above code to all #Entity-classes?
(If it matters for your answer: I'm using a H2-Database.)

If your database and tables support AUTO_INCREMENT change the annotation to this #Id #GeneratedValue(strategy=GenerationType.IDENTITY). Then the id will be generated during commit.
There is another way via TABLE or SEQUENCE strategy, but it needs to be explicitly defined per Entity which is problem for abstract BaseEntity. Just hava a look:
#Entity
#TableGenerator(name="tab", initialValue=0, allocationSize=50)
public class EntityWithTableId {
#GeneratedValue(strategy=GenerationType.TABLE, generator="tab")
#Id long id;
}
EDIT:
Well, so it is possible!
MappedSuperclass - Change SequenceGenerator in Subclass

#Id
#Column(name = "ID", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "SEQ_AAAAAAAAAAA")
#TableGenerator(
name = "SEQ_AAAAAAAAAAA",
table = "SEQ_ENTITY" /*<<<==YOUR TABLE NAME FOR SAVE NEXT VALUES HERE*/,
pkColumnName = "ENTITY",
initialValue = 1,
valueColumnName = "NEXT_ID",
pkColumnValue = "packageee.PACK.PAK.YOURCLASSNAME",
allocationSize = 1)
private Long id;

Related

Hibernate querying the entity after fetching its collection

We have the following 2 classes
public class StagingConcept implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_seq")
#Column(name = "id")
private long id;
#Column(name = "concept_id", nullable = false, length = 18)
private String conceptId;
#OneToMany(mappedBy = "concept", fetch = FetchType.LAZY,
cascade = { CascadeType.ALL })
private Set<StagingConceptDescription> descriptions;
// rest of the class
}
public class StagingConceptDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_desc_seq")
#Column(name = "id")
private long id;
#ManyToOne
#JoinColumn(name = "concept_id", referencedColumnName = "concept_id")
#ForeignKey(name = "stg_concept_desc_fk1")
private StagingConcept concept;
// rest of the class
}
Some of the details, such as other class properties and entity annotations, have been omitted to keep the example precise. Please let me know if you need more details. Yes, the FK from StagingConceptDescription to StagingConcept is a non-PK Foreign Key.
When I create a Criteria:
"from " + StagingConcept.class.getCanonicalName()
I get all the StagingConcept entities from the DB in one single query. But I need to get the descriptions for each StagingConcept. For that, I write a query:
"from " + StagingConcept.class.getCanonicalName() + " join fetch descriptions"
The resulting SQL looks like:
select stagingcon0_.id as col_0_0_,
descriptio1_.id as id178_1_,
stagingcon0_.is_active as is2_149_0_,
stagingcon0_.brand_restriction_status as brand3_149_0_,
stagingcon0_.concept_id as concept4_149_0_,
stagingcon0_.container_type as container5_149_0_,
stagingcon0_.controlled_drug_status as controlled6_149_0_,
stagingcon0_.effective_date as effective7_149_0_,
stagingcon0_.form as form149_0_,
stagingcon0_.is_multi_component as is9_149_0_,
stagingcon0_.namespace as namespace149_0_,
stagingcon0_.preferred_term as preferred11_149_0_,
stagingcon0_.source as source149_0_,
stagingcon0_.source_version as source13_149_0_,
stagingcon0_.subsidy_status as subsidy14_149_0_,
stagingcon0_.type as type149_0_,
stagingcon0_.unit_of_use_size as unit16_149_0_,
stagingcon0_.unit_of_use_size_unit as unit17_149_0_,
descriptio1_.is_active as is2_178_1_,
descriptio1_.concept_id as concept6_178_1_,
descriptio1_.is_preferred as is3_178_1_,
descriptio1_.term as term178_1_,
descriptio1_.type as type178_1_,
descriptio1_.concept_id as concept6_149_0__,
descriptio1_.id as id0__
from stg_concept stagingcon0_
inner join stg_concept_description descriptio1_ on stagingcon0_.concept_id=descriptio1_.concept_id
It does fetch all the StagingConcepts and their descriptions, albeit in a slightly larger result set in that SQL.
All looks fine up until here. But then it goes and tries to find a staging concept for each and every description again. So if I have 30000 Staging Concepts and 60000 descriptions, it will send another 60000 queries to fetch the staging concept for every description. This looks a little nasty and takes up huge amount of time, enough to run past the transaction timeout.
To attempt to resolve this issue, I change the StagingConceptDescription to
public class StagingConceptDescription {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence")
#SequenceGenerator(name = "sequence", sequenceName = "stg_concept_desc_seq")
#Column(name = "id")
private long id;
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "concept_id", referencedColumnName = "concept_id")
#ForeignKey(name = "stg_concept_desc_fk1")
private StagingConcept concept;
// rest of the class
}
So, the ManyToOne relationship is now set to LAZY, explicitly. And also, the relationship states that the concept is not optional, in an attempt to indicate that the relationship is non-optional. By setting that, I meant to tell hibernate that it should be OK to create a proxy object, if needed, because the other end of the relationship is always going to be present. But none of this had any effect. Not too sure if this would work.
I have also tried the #Fetch annotations, etc. None of them work. Even setting it to #LazyToOne(LazyToOneOption.PROXY) didn't have any effect.
Based off https://stackoverflow.com/a/29863982/5464931, you can do another join fetch.
e.g.
"from " + StagingConcept.class.getCanonicalName() + " join fetch descriptions join fetch descriptions.concept"
But this isn't the best solution because it still queries the entity/parent/concept again which is unnecessary.

How to define allocationSize and initialValue for all entities

On all my entities, I defined allocationSize and initialValue manually, just like this :
#Id
#SequenceGenerator(name = "ID_GENERATOR", sequenceName = "SEQUENCE_ID", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_GENERATOR")
#Column(name = "ID")
private Long id;
Is it possible to set these attributes globally for all my entities ?
If so, how ?
According to the SequenceGenerator's documentation:
The scope of the generator name is global to the persistence unit
(across all generator types).
So you can define one at in package-info.java as stated in the reference documentation:
#GenericGenerators({
#GenericGenerator(
name = "uuid2",
strategy = "uuid2"
)
})
package your.package;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.GenericGenerators;
Then you need to add you package to the SessionFactory Configuration:
configuration.addPackage("your.package");
And then you can reuse these common generators in all your entities:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_GENERATOR")
#Column(name = "ID")
private Long id;
Generator and GeneratedValue are two different things. Once Generator is set (via annotation or xml) it is accessible for whole persistence unit. GeneratedValue describes how given id should be generated (in your case it points to one of the generators).
You cannot set allocationSize nor initialValue for all Generators in advance, but you can set it for one generator and use it for many entities.
Please also note that Hibernate has default value of 50 for allocationSize.

How to generate ID for entity from the existing ORACLE sequence?

I have a Table (for a long time ago), call it TABLE_A, and I have an entity class for this Table:
#Entity
#Table(name = "TABLE_A")
public class TableA implements Serializable {
#Id
#Basic(optional = false)
#Column(name = "ID")
//what else should I write here, to get the value from the existing sequence (seq_table_a_id) from database?
private Long id;
#Basic(optional = false)
#Column(name = "VALID_TO_DT")
private String name;
getters/setters...
}
I had created a sequence for this table in ORACLE a long time ago, and I want to give values for the new item's ID from this sequence. How should I write this code in java entity with annotations? If you could write an example for my code, that would be helpful!
And should I write anything else maybe in the persistance.xml?
The name of the existing sequence is: seq_table_a_id
You should check the annotation #GeneratedValue and #SequenceGenerator
#Id
#GeneratedValue(generator="seqGen")
#SequenceGenerator(name="seqGen",sequenceName="seq_table_a_id", allocationSize=1)
private Long id;
Check this link

Define primary and foreign keys in same object which use the sequence generators

I have created Address object which has a ID (AddressPK) which get generated by a sequence. The address object also has a foreign key to Client object via the ID of the client object (ClientPK). The ClientPK also generated via a sequence. However, it complains at the time I try to deploy the ear to web logic server saying that only one generated element can exist for an entity. The class snippets as follows. Anyone can help how to define a foreign key using an object that also uses a sequence generator in addition to the object primary key?
public class Address implements Serializable{
#EmbeddedId
private AddressPK id;
#Embedded
private ClientPK clientId;
...
}
#Embeddable
public class AddressPK implements Serializable {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="RoidSequenceGenerator")
#SequenceGenerator(name="RoidSequenceGenerator",sequenceName="roid_sequence", allocationSize=1)
#Column(name = "a_roid")
private long value;
...
}
#Embeddable
public class ClientPK implements Serializable {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="RoidSequenceGenerator")
#SequenceGenerator(name="RoidSequenceGenerator",sequenceName="roid_sequence", allocationSize=1)
#Column(name = "c_roid")
private long value;
...
}
I think you do not understand what is the Primary Key and what the difference from the Foreign Key. Primary key (PK) can be generated for each database entity. And there is a restriction in JPA that generated value must be only one. Foreign Key (FK) in the other hand is a link to the PK of another entity. So you need to create another entity with generated PK and have a link to it with OneToMany, ManyToMany or another kind of association. For example:
#TableGenerator(name = "entities_id_generator", table = "SEQUENCE",
pkColumnName = "SEQ_NAME", valueColumnName = "SEQ_COUNT", pkColumnValue =
"ENTITY_SEQ", initialValue = 0, allocationSize = 1)
public class DBObject1 implements Serializable {
#Id
#GeneratedValue(generator = "entities_id_generator", strategy = GenerationType.TABLE)
private Long id;
#ManyToOne
#JoinColumn(name = "source_id")
private DBObject2 source;
}
#TableGenerator(name = "entities_id_generator", table = "SEQUENCE",
pkColumnName = "SEQ_NAME", valueColumnName = "SEQ_COUNT", pkColumnValue =
"ENTITY_SEQ", initialValue = 0, allocationSize = 1)
public class DBObject2 implements Serializable {
#Id
#GeneratedValue(generator = "entities_id_generator", strategy = GenerationType.TABLE)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "source")
private List<DBObject1> objects;
}
In example above the virtual FK for entity DBObject1 will be stored in column source_id. It is virtual because there will not be any physical FK in database table. But JPA will link these objects if you ask.

Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)

I'm usign #GeneratedValue(strategy = GenerationType.AUTO) to generate the ID on my entity.
I don't now how it works, but on my child table, generates ID values, that follow the parent sequence.
//parent table
#Entity
#Table (name = "parent")
public class Parent {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
#Column (name = "id")
private long id;
#OneToMany (cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
#JoinColumn (name = "parentId")
#ForeignKey (name = "FKparent")
private List<child> child;
}
//child table
#Entity
#Table (name = "child")
public class Child {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
#Column (name = "id")
private long id;
}
The inserted ID values on parent, updates the sequence.
The inserted ID values on child, updates the sequence.
On the next insert of parent, the sequence... uses values updated by child insertions...
This Annotations, aren't creating two sequences, only one. Is this correct/expected?
I inserted my entities with my DAO service only using entityManager.persist(parent);
These Annotations are no creating two sequences, only one. Is this correct/expected?
That's the expected behavior. When using #GeneratedValue(strategy = GenerationType.AUTO), the JPA provider will pick an appropriate strategy for the particular database. In the case of Oracle, this will be SEQUENCE and, since you did not specify anything, Hibernate will use a single global sequence called hibernate_sequence.
Is this correct? Well, I don't know, it depends on your needs. Just in case, the default maximum value for an Oracle sequence is 1E+27, or 1,000,000,000,000,000,000,000,000,000. That's enough for many.
Now, it is possible to use GenerationType.AUTO and still control the name of the sequence when the database uses sequences:
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="my_entity_seq_gen")
#SequenceGenerator(name="my_entity_seq_gen", sequenceName="MY_ENTITY_SEQ")
private Long id;
Yes this is correct and expected.
You can create individual sequences for each table, but IMHO that is just extra code with no actual benefit.
#Entity
#Table(name = "table_seq")
#SequenceGenerator(name= "NAME_SEQUENCE", sequenceName = "SEQ_ID", initialValue=1, allocationSize = 1)
public class SeqEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="NAME_SEQUENCE")
private Long id;
}

Categories