Import data using JPA keeping same identifiers - java

I've developed a simple web app which use some database tables using JPA. Let's say Table A and Table B with reference to Table A. My app runs in different environments on my customers like development, testing and production stage. Because of that I have to create a process to export/import data between these environments.
When I move the objects I want to keep the same ID because I want to keep the same references. Is there any way to do that with JPA?
I am using Hibernate 4.3.8 with Oracle Database. This is my Entity:
#Entity
#Table
public class Category
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
And I am trying to do that:
Category category = new Category();
category.setId(1L);
category.setName("Test");
EntityManager em = PersistenceManager.getEntityManager();
em.merge(category);
But it uses the hibernate sequence to generate the ID instead of using the given one. If I use persist instead of merge it throws an exception because it is a detached object.
Is there any way to do that ?

Yes, you can define a custom id generator using a defined sequence
You need a class which extends org.hibernate.id.SequenceGenerator:
package yourPackage;
public class CustomGenerator extends SequenceGenerator
{
#Override
public Serializable generate(SessionImplementor session, Object obj)
{
...
}
}
And to use your custom generator in your entity:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CustomGenerator")
#GenericGenerator(name = "CustomGenerator",
strategy = "yourpackage.CustomGenerator",
parameters = {
#Parameter(name = "sequence", value = "custom_id_sequence")
})
private Long yourId;

Related

Auto generated id is generated based on previous JpaRepository.save()

I have two entity class:
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// getter and setter
}
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
// getter and setter
}
I have the repositories which extends JpaRepository.
A a = new A();
B b = new B();
aRepo.save(a); // generates id = 1
bRepo.save(b); // generates id = 2
Since I am inserting to different tables, I would like the ids to be generated based on the previous id in that particular table and not depending on the previous insertion in some other table. Is there any way to do this?
I am using MySQL as my db.
I think what you're looking for is GenerationType.IDENTITY.
GenerationType.AUTO vs GenerationType.IDENTITY in hibernate may help you

Hibernate does not allow to save data with similar id

I'm learning Spring and few days ago i started learning Hibernate. I have my studying project where i need to create a shop with products. Here is entity class
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#Entity
#Table(name = "cart")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "quantity")
private int quantity;
#Column(name = "mask")
private String mask;
#Column(name = "price")
private int price;
So, i create interface
import org.springframework.data.jpa.repository.JpaRepository;
public interface CartRepository extends JpaRepository<Cart, Integer> {
}
and create controllers
#Autowired
CartRepository cartList;
#RequestMapping("/add-black-mask")
public String addBlackMask() {
cartList.save(new Cart(1, 1, "black", 3));
return "masks/add-black-mask";
}
#RequestMapping("/add-build-mask")
public String addBuildMask() {
cartList.save(new Cart(2, 1, "build", 5));
return "masks/add-build-mask";
}
#RequestMapping(value = "/save_product_to_cart")
public ModelAndView saveProduct(#ModelAttribute(value = "cart")
Cart cart, BindingResult result) {
int index = Integer.parseInt(String.valueOf(cartList.count()));
if (cart.getId() == 0) {
cart.setId(index + 1);
cartList.save(cart);
} else {
Cart cart1 = cartList.getOne(cart.getId());
cart1.setMask(cart.getMask());
cart1.setQuantity(cart.getQuantity());
cart1.setPrice(cart.getPrice());
cartList.save(cart1);
}
return new ModelAndView("redirect:/");
}
Also, there are some other controllers for view, Thymeleaf etc, but its ok. My problem is - when i save my product 1 time - its ok, but when i save second - it didnt work( i think because i can't save 2 rows with similar ID) So it seems i have UNIQUE ID in my table and it can not be repeated. Question is - how can i delete unique id or change my code in any way? Thanks in advance!
p.s. i read some other topics here but it didnt help me.
when you use GENERATIONTYPE.IDENTITY you are asking hibernate to let the database handle Ids for you, you should not set it yourself. you are changing the value of the id, just create a new product, set all the fields and inside a transactional context, save your product. Also always use wrapped versions of primitives for serialization purposes. (Long is an object but long is a primitive.) you can google boxing and unboxing and learn more about this.
Let me answer this question:
First of all, using annotations as our configure method is just a convenient method instead of coping the endless XML configuration file.
The #Idannotation is inherited from javax.persistence.Id, indicating the member field below is the primary key of current entity. Hence your Hibernate and spring framework as well as you can do some reflect works based on this annotation. for details please check javadoc for Id
The #GeneratedValue annotation is to configure the way of increment of the specified column(field). For example when using Mysql, you may specify auto_increment in the definition of table to make it self-incremental, and then use
#GeneratedValue(strategy = GenerationType.IDENTITY)
in the Java code to denote that you also acknowledged to use this database server side strategy. Also, you may change the value in this annotation to fit different requirements.
1. Define Sequence in database
For instance, Oracle has to use sequence as increment method, say we create a sequence in Oracle:
create sequence oracle_seq;
2. Refer the database sequence
Now that we have the sequence in database, but we need to establish the relation between Java and DB, by using #SequenceGenerator:
#SequenceGenerator(name="seq",sequenceName="oracle_seq")
sequenceName is the real name of a sequence in Oracle, name is what you want to call it in Java. You need to specify sequenceName if it is different from name, otherwise just use name. I usually ignore sequenceName to save my time.
3. Use sequence in Java
Finally, it is time to make use this sequence in Java. Just add #GeneratedValue:
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
The generator field refers to which sequence generator you want to use. Notice it is not the real sequence name in DB, but the name you specified in name field of SequenceGenerator.
4. Complete
So the complete version should be like this:
public class Cart
{
#Id
#SequenceGenerator(name="seq",sequenceName="oracle_seq")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
private Integer id;
}
Now start using these annotations to make your JavaWeb development easier.
On top of that I would like you to understand all 4 ways of ID generation in hibernate. You can think of reading in your free time
GenerationType.AUTO
GenerationType.IDENTITY (your case)
GenerationType.SEQUENCE
GenerationType.TABLE {Rarely used nowdays}

How to provide Initial value OR Increment ID with JPA GenerationType.AUTO

I am using following code to define MyEntity,
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
For the first POST after my application starts, I create MyEntity everything works fine, MY_TABLE_ID starts with 1 and works as expected.
My issue is, If somebody inserts data manually before I do my POST then I get duplicate key exception as myTableId is entered as 1 which is already present.
My main problem is I can't create database sequence for using GenerationType.SEQUENCE now to resolve this as database can't be altered now.
I have tried various combinations of GenerationType, TableGenerator but I am unable to successfully tackle it.
Setting initialValue to some larger number to avoid duplicate values can temporarily resolve my problem but I am unable to do it too.
If someone can help me with initialValue with AUTO or give me some other better solution without database changes will be great :)
As MY_TABLE_ID is an identity column, following annotations will work.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY) // <-- IDENTITY instead of AUTO
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
The identity column will automatically assign an value as soon as the transaction is committed. You are not to set any values for an identity column, as its the job of the database to assign the values. Therefore you also don't need to think about any initial values (forget them completely for identity columns)
I tried various options in answers provided here and for similar questions on stackoverflow and other forums,
I had few limitations,
I couldn't create database sequence as my database changes were freezed.
I didn't want to introduce new Custom IdGenerator class because it would add confusion to other people working with me.
It was resolved using following change:
Adding GenericGenerator with increment strategy helped me, I made following changes to my code.
#Entity
#Table(name = "MY_TABLE")
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator="seq")
#GenericGenerator(name = "seq", strategy="increment")
#Column(name = "MY_TABLE_ID")
private Integer myTableId;
#Column(name = "MY_TABLE_NM")
private String myTableName;
//Getters Setters
}
It helped me because,
From Hiberbate DOCs
increment
An IdentifierGenerator that returns a long, constructed by counting
from the maximum primary key value at startup. Not safe for use in a
cluster!
Since, it was incrementing already existing myTableId even if it was manually inserted, this resolved my issue.
You can also implement your own generator if you need more control.
See this interface IdentifierGenerator.
So you can get the count of records, for example through a #NamedQuery.
Then you can generate an identifier yourself.
public class MyEntityKeyGenerator implements IdentifierGenerator {
#Override
public Serializable generate(SessionImplementor session, Object object) {
// SELECT count(ent) from MyEntity ent;
Long count = (Long) session.getNamedQuery("count-query").uniqueResult();
// calc and return id value
}
}
Entity:
class MyEntity {
#Id
#GenericGenerator(name = "my_generator",
strategy = "org.common.MyEntityKeyGenerator")
#GeneratedValue(generator = "my_generator")
private Long id;...
Just do not forget about the lock.
I use the generation type Identity, which basically means that the db, takes care of Id generation.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#MappedSuperclass
#EntityListeners(EntityListener.class)
#EqualsAndHashCode(of = {"id", "createdAt"})
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATED_AT", updatable = false)
private Date createdAt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATED_AT")
private Date updatedAt;
}
You can also use, Sequence generation:
#Entity
#SequenceGenerator(name="seq", initialValue=1, allocationSize=100)
public class EntityWithSequenceId {
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq")
#Id long id;
}

Spring relationship get child id without load sub class

How to get DEPARTMENT_ID of the base class without loading sub-class in Spring boot JPA
For example we have a base model:
#Entity
#Table(name = "TM_POSITIONS")
public class PositionEntity {
#Id
#SequenceGenerator(name = "PositionsSequence", sequenceName = "TM_POSITIONS_SEQ", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PositionsSequence")
private long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH)
#JoinColumn(name = "DEPARTMENT_ID")
private DepartmentModel department;
...
So, how to just get DEPARTMENT_ID without bundle it and load another object. In some cases, I need to get related Model and in some cases, I need to get just DEPARTMENT_ID.
You can create a responseDTO class and a mapper class for that.
#ApiModel
public class positionEntityResponseDTO{
private long id;
private long departmentId;
//getter & setter
}
And the mapper class look like
#Service
public class ResponseMapper{
public positionEntityResponseDTO map (PositionEntity entity){
positionEntityResponseDTO response = new positionEntityResponseDTO();
response.setId(entity.getId);
response.setDepartmentId(entity.getDepartment.getId();
return response;
}
}
now suppose somewhere you write
#Autowired
private ResponseMapper mapper;
positionEntityResponseDTO response= mapper.map(repository.save(entity));
now you can pass only DEPARTMENT_ID by response object. hope this works for you.
First of all you need to set the fetch = FetchType.LAZY. In the EAGER way the sub-class will always be loaded.
If you set the fetch type lazy, you can reach the sub-class's id (primary-key) field without extra db queries.
So if you write position.getDepartment().getId() you will get the id and it won't cost anything.
Keep in mind, that other method calls on the sub-class will be load it from the database, e.g.: toString, equals, or getName() /if there is such a method/.
In the case you need the sub-class for some other functionalities, you should write another query in your reposiroty which will fetch the sub-class too.
#Query("SELECT pos FROM Position LEFT JOIN FETCH pos.department")
With JOIN FETCH Spring Data JPA will generate a single query which will join the two table, so you can avoid the N+1 problem.

override identitycolumn hibernate mssql

I have to manage a Datatransfer between 2 DBs (mssql) with hibernate.
When i load an object from one DB with session.get() it already has a private key. Then i need to persist it to the other DB with anotherSession.replicate(Object o).
My Problem ist, that the given PK is not persisted but replaced by another one.
PS: Both the srcTable and the destTable have PK generation Identity and it needs to stay that way.
If you map an Entity ID with generation "identity", Hibernate will always generate a new ID as soon as you try to persist it. You will have to switch the generation to "assigned" to keep your old ID.
If you have something like it
#Entity
public class Project {
#Id #GeneratedValue long id; // still set automatically
}
You have to remove the #GeneratedValue annotation from id field. Else jpa will generate a value for id before insertion.
#Entity
public class Project {
#Id long id; // must be initialized by the application
:
}
Solution To Your Problem
Create a an entity containing all the mapping definition.
Create a ID field in your new class without the #Generated value annotation.
Clone the old entity to this new one.
Persist this new entity.
Now if you create a subclass extending your entity then the whole
process becomes very easy.
Sample Code For This Solution
Existing Entity
#Entity
#Table(name="EJB_PROJECT")
public class OldEntity implements Serializable {
#Id
#Column(name="PROJECT_ID", primaryKey=true)
#GeneratedValue
Integer id;
}
New Entity
#Entity
#Inheritance(strategy=SINGLE_TABLE)
#Table(name="EJB_PROJECT")
public class NewEntity extends OldEntity {
#Id
#Column(name="PROJECT_ID", primaryKey=true)
Integer id;
// Constructor to clone old entity's id
public NewEnity(OldEntity old) {
this.id = old.id;
}
}
Persisting code
em.persist(new NewEntity(oldEntity));

Categories