I have a use case of dual write where I need to save entities to both Oracle and Postgres DB. I also want to implement a custom IdentifierGenerator to create new primary keys, which have to be consistent in Oracle and Postgres.
The entity is like below (Getter and Setter are ignored).
#Entity
#Table(name = "users")
#GenericGenerator(
name = "CUSTOM_ID_GENERATOR",
strategy = "<class reference>",
)
public class User implements Serializable {
#Id
#GeneratedValue( generator = "idGenerator")
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
}
When saving the entity, what I'm gonna do is like:
User user = new User();
user.setName("USER_1");
long id = oracleSession.save(user);
user.setId(id);
postgresSession.save(user);
For my code, will it cause a new id to be generated in Postgres? If so, how should the implementation be to keep the same id in both Oracle and Postgres?
Is there a way to let the IdGenerator NOT generate a new value when the id is manually set?
Thanks in advance.
#GeneratedValue will cause new value each time save method is called. So you don't have guarantee that the value would be same for Oracle and PG.
You need to use assigned Id in this case.
Your application should generate the id and should set to the user object. The assigned Id will then be saved to both the DB.
To do this you have to drop the #GeneratedValue annotation refer this documentation page.
Now the next question is how to generate unique Id for your tables, the simplest way is to use a sequence from either DB and get new value and set that to user object. Go through this question for more information on this.
Related
How to properly delete a record from a database in one query. For example, when an entity uses the primary key of the parent entity using the #MapsId annotation, if the parent entry is deleted, it will swear that the parent's id is used in the child entity.
Code example :
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String name;
}
#Entity
public class UserDetails {
#Id
private long id;
private String phone;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User user;
}
Here, when deleting a User using the JpaRepository delete method, an error will occur that the UserDetail uses the primary key User
First, are you sure the direction of the relation makes sense? I would have expected it to be the other way around, because the user ID and name seem to be the more basic info that you need more often.
Second, what you're doing seems like an attempt to optimize performance, because you could just as well store all the data in a single entity. Are you sure the optimization pays off? (I would guess not.) See Premature Optimization.
Third, if the relation was the other way around, you could modify the annotation to #OneToOne(cascade=CascadeType.DELETE) or #OneToOne(cascade=CascadeType.ALL) to let JPA automatically delete the other entity when the first is deleted.
For that you need to delete all foreign keys with used by primary key
or by using cascade
After that use below
In JPA we can use deleteById
or by named query
DELETE FROM EMPLOYEE WHERE ID = ?1
or my native query
delete from employee where id = ?1
I have a JpaRepository persisting newly created entity in Spring MVC app. This entity looks like this (very simplified):
#Entity
public class Translation {
.....
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private Version version;
....
}
and Version entity:
#Entity
public class Version {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#Column(name = "version_code")
private long code;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "version", cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<Translation> translations;
}
I create a translation object like this
TranslationDTO t = new TranslationDTO();
t.setText(translationText);
ClientVersionDTO version = new ClientVersionDTO();
version.setId(11);
t.setVersion(version);
where 11 is a version that exists in the database already from the very beginning. Please notice that I do not set values for name and code of ClientVersionDTO.
Then I have a service that persists new object (I use dozer library to convert DTO to entities)
#Service
#Transactional
public class TranslationsServiceImpl implements TranslationsService {
#Override
public Long create(TranslationDTO translationDTO) {
Translation translation = translationsConverter.unconvert(translationDTO);
Translation t = translationRepository.saveAndFlush(translation);
Translation t2 = translationRepository.findOne(t.getId());
// !!!! t2.getVersion() returns version where no values are set to 'code' and 'name'
return t2.getId();
}
}
Please notice my comment "t2.getVersion() returns version where no values are set to 'code' and 'name'" - I was expecting so that when I fetch the data from the database, I would get a Version object right from the database with code and name values set. However they are not set. So basically what I get as a t2.getVersion() object is the same object as in input argument translationDTO.getVersion(). How can they I re-invalidate the Version object?
UPDATE tried moving #Transactional to JpaRepository, but still the same result.
If you are using Hibernate, this is the expected result. When you call translationRepository.saveAndFlush(translation) and translationRepository.findOne(t.getId()) one after the other, they hit the same Hibernate session which maintains a cache of all objects that it has worked on. Therefore, the second call simply returns the object passed to the first. There is nothing in those two lines that would have forced Hibernate to fire a SELECT query on the database for the Version entity.
Now, the JPA spec does have a refresh method on the EntityManager interface. Unfortunately, Spring Data JPA does not expose this method using its JpaRepository interface. If this method was available, you could have done t = translationRepository.saveAndFlush(translation) and then versionRepository.refresh(t.getVersion()) to force the JPA provider to synchronize the version entity with the database.
Implementing this method is not difficult. Just extend SimpleJpaRepository class from Spring Data JPA and implement the method yourself. For details see adding custom behaviour to all Spring Data JPA repositories.
An alternate would be to load the version entity as versionRepository.findOne(version.getId()) before setting it on the translation. Since you can hard-code version id in your code, your versions seem to be static. You can therefore mark your Version entity as #Immutable and #Cacheable (the former is a Hibernate-specific annotation). That way, versionRepository.findOne(version.getId()) should not hit the database every time it is called.
I'm using Hibernate 5 and Spring 3.2.4. I'm deigning a User entity in which I want to include a reference to the user that has created the entity - so a self reference. The self reference itself isn't too problematic, but I want to specify the field as non null. Is this possible? How do I create the first entry in the DB if the field is non null as there referenced entity does not already exist?
Ex:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long User.id;
#NotNull
private String username;
private String password;
#NotNull
private User user;
// getters and setters omitted for brevity
}
If I try:
User u = new User();
u.setUsername("username");
u.setCreatedBy(u);
and try to persist u, I get the following error message:
Caused by: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation: com.domain.User.createdBy -> com.domain.User
I understand that Hibernate is complaining that it cannot reference a transient instance (ie: u), but I cannot persist u unless I have an non-null User that I can reference. But in an empty DB, there is no such entry.
Is this kind of configuration impossible to do? Or is there a way around this?
I don't understand this Roo annotations and I don't use Hibernate-specific annotations, only JPA. I don't have any issues with self references. But I have some hints fo you:
As mentioned before, use #ManyToOne annotation.
AFAIK, #NotNull annotation (or nullable field in #Column) does not affect mapping, only DDL generation. I don't use DDL generation from domain model, do I never specify this. Instead I use optional field of #ManyToOne.
What identifier generation strategy you use? If autoincrement, self-references are impossible with NOT NULL constraint. So either use sequence-based identifier generator or remove constraint. I would use first.
As I mentioned, set optional field of #ManyToOne to false, when you have NOT NULL constraint. Otherwise Hibernate attempts to make two queries: insert with createdBy_id set to NULL and then update createdBy_id. And the first query fails with NOT NULL contraint enabled.
I found a solution for this. You must use a Sequence generator for your ID (instead of the default auto generated IDs). Then it works.
#Entity
public class UserModel {
/**
* We must use a sequence for generating IDs,
* because of the self reference .
* https://vladmihalcea.com/2014/07/15/from-jpa-to-hibernates-legacy-and-enhanced-identifier-generators/
*/
#Id
#GeneratedValue(generator = "sequence", strategy=GenerationType.SEQUENCE)
#SequenceGenerator(name = "sequence", allocationSize = 10)
private Long id;
/** reference to a parent user, e.g. the manager */
#ManyToOne(optional = false)
#NotNull
UserModel parentUser;
[...]
The reason is the following: When Hibernate tries to insert a new User it also tries to validate the reference to the parentUser. But that will fail, for the first user we want to insert, or will also fail when a user references himself.
But when IDs are generated with a sequence, then the new/next ID is already known at the time of insert.
The field createdBy of u can not be null because of the annotation #NOTNULL. But u referes to itself and its not persist before saving it.
You can set another persisted User for u, not itself.
I have searched on the web before posting this here, but I'm in an important project and I don't have any more time to waste with this. Well, here's the deal:
My tables in the database(SqlServer) are created automatically by Hibernate. So, I have an Entity and this entity was mapped before with Id annotation, like this:
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private int id;
Hibernate created a table dbo.Parcela inside sqlServer, but we had to change the way we generate the id, Because sometimes we receive the id number and we want that id saved on our database. So our Entity now is like this:
#Id
private Integer id;
Things work fine when we run the program for the first time, but we have some customers that already have their databases created with old Id mapping and we cannot create a new table, with the new Id mapping. So when I'm trying to insert a new record I get this message:
SEVERE: Cannot insert explicit value for identity column
in table 'Parcela' when IDENTITY_INSERT is set to OFF.
Any help would be appreciate.
Thanks
So you want your surrogate keys generated by the database, except when they were already generated by the customer. How are you going to avoid collisions, if the database wants to set id=12345, but a customer-imported entry with that id already exists?
The short answer to your question is: don't do this. I don't want to go into the old natural key vs surrogate key debate, this has been done already for example here. And google "codd surrogate keys" to learn how to properly use them. All i want to say is: if you use surrogate keys, then have your database generate them, and treat everything from outside as additional lookup key. That's the sane way.
The long answer is: if you really want to do this, and if you really know what you're doing, you can implement your own IdGenerator class. In JPA for example, you could annotate your id:
#Id
#GenericGenerator(name = "customId", strategy = "com.example.CustomIdGenerator", parameters = { #Parameter(name = "sequence", value = "SEQ_IDGENERATOR") })
#GeneratedValue(generator = "customId")
#Column(name = "ID", unique = true, nullable = false)
private Integer id;
Your customIdGenerator would then extend SequenceGenerator:
public class CustomIdGenerator extends SequenceGenerator {
public Serializable generate(SessionImplementor session, Object obj) {
// return o.getId() if obj.getId() is not null
// newId = SEQ_IDGENERATOR.nextval
// while (newId is already in db) newId = SEQ_IDGENERATOR.nextval
// return newId
}
}
and your database would provide SEQ_IDGENERATOR. Id would no longer be an autogenerated field but simply
create table foo( id integer not null primary key, ...);
But again: you don't want to do this. You want your surrogate keys to be irrelevant to the outside world and handled by the database.
How did you have Hibernate create the schema for the DB? If you used hbm2ddl perhaps adding
<property name="hibernate.hbm2ddl.auto" value="update"/>
to your persistence.xml or setting hbm2ddl.auto to "update" may have Hibernate automatically update the db schema on redeploy, having it fix the insertion problem.
Of course it won't help you in cases when you try inserting an already existing id, but i guess you know it :)
I am using hibernate only with Annotations. My table looks something like this:
#Entity
#Table(name = "NetworkType",
uniqueConstraints = {#UniqueConstraint(columnNames = {"network_id", "type"})})
public class NetworkType implements Serializable {
#Id
private long id;
#Column(name = "network_id", nullable = false)
private long networkId;
#Column(name = "type", nullable = false)
private String type;
...
Currently when I write the same NetworkType twice, it throws an exception due to the UniqueConstraint (which is expected).
My thoughts are to just read the item first before checking. The problem is, my primary key is the Id, which I need because other tables references this table.
What's the best way to query for item for the "network_id" and "type" to verify the combination doesn't already exist?
I know I can do this with a Query manually, but is there a more Hibernate-y way of doing it?
In general, what's the proper way to "get" an object without using the PK? Are Criteria or Query the best way?
#UniqueConstraint is mainly used by database schema generation tools to create the data base schema. If used, they will generate the table with the columns mentioned in the #UniqueConstraint having unique constraint defined.
#UniqueConstraint doesn't have any impact/usage during data manipulation.
If you wish to achieve unique constraint behavior on network_id and type columns and your schema is already created, update your database schema to add the unique constraint on network_id and type columns. as below:
ALTER TABLE NetworkType
ADD CONSTRAINT uc_network_id_type UNIQUE (network_id, type)
Hope this helps!