How to tell Hibernate to conditionally ignore columns in CRUD operations - java

Is it possible to somehow tell Hibernate to conditionally ignore a missing column in a database table while doing the CRUD operations?
I've got a Java application using Hibernate as persistence layer. I'd like to be able to somehow tell Hibernate: If database version < 50, then ignore this column annotation (or set it transient).
This situation arises due to different database versions at different clients, but same entity code for all sites. For example, I've got a class, where the column description2 might miss in some databases.
#Entity
#Table(name = "MY_TABLE")
public class MyTable implements java.io.Serializable {
private Integer serialNo;
private String pickCode;
private String description1;
private String description2;
#Id
#Column(name = "Serial_No", nullable = false)
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
public Integer getSerialNo() {
return this.serialNo;
}
#Column(name = "Pick_Code", length = 25)
public String getPickCode() {
return this.pickCode;
}
#Column(name = "Description1")
public String getDescription1() {
return this.description1;
}
#Column(name = "Description2") // <- this column might miss in some databases
//#TransientIf(...) <- something like this would be nice, or any other solution
public String getDescription2() {
return this.description2;
}
}
Background: I have a large application with a lot of customizations for different clients. Now it happens from time to time that one client (out of lets say 500) gets a new feature that requires a database structure update (e.g. a new field in a table). I release a new version for him, he runs a database schema update and can use the new feature. But all other clients won't do an incremental database update each time when any user gets a new feature. They just want to use the latest version, but are affected by the new feature (for that one client) they will never use.

I think it is only possible if you separate the mapping definition from the entities so that you can replace it. Thus you can not use annotation based mapping.
Instead I would suggest to use xml based mapping and create different xml mapping files for each client. Since you have about 500 clients you might want to create groups of clients who all share the same mapping file.
At least I think it will be very hard to maintain the different clients needs with one entity model and it will lead to a complex code structure. E.g. if you add properties to the enties that can be null for some clients than you will also add a lot more null checks to your code. One null check for each client specific property.

Related

Spring's Entity inside nodejs

The way I manage persistent state inside my backends in the past is by using Spring's #Entity. Basically, I define a regular java class like this:
#Entity
#Table(name = "users")
class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
public Long user_id;
#Column(name = "email")
public String email;
}
Then I can use Hibernate to retrieve a managed java object:
User user = userFactory.getUserFromId(3L); // uses Hibernate internally to get a managed instance
user.email = "abc#company.com"; // gets auto-save to mysql database
This is highly convenient, as I can just change the fields without explicitly worrying about saving data. It's also quite convenient as I can specify a number of fields to be used as an index, so I can search for matching email names quite fast.
How would I go about doing the same thing using NodeJS? I need to use node as my team is not familiar with Java at all. I want to be able to store complex json objects fast, have a cached version in memory that ideally stores data permanently at regular intervals.
My current plan is to use Redis for this. Getting user's object should look something like this:
class User {
constructor(json) {
this.json = json
}
async save() {
await redis.set(JSON.stringify(this.json));
}
}
async function user(id) {
let json = JSON.parse(await redis.get("user-" + id));
return User(json);
}
u3 = await user(3);
u3.json.email = "def#company.com";
u3.save();
To get user by name, I'd create my own index (mapping from email to user id), and potentially store this index inside redis as well.
All of this seems clunky, and feels like I'm reimplementing basic database features. So before I do it like this, are there different ways to manage json objects well in js, so that the coding experience is somewhat the same as in Spring?
What you need is an ORM, that is that tool in every language that maps your objects into database records.
With a quick search you can find Sequalize that is very popular in the NodeJS world.

Hibernate JPA uniqueness

Let's say that this is a class that has unique constrained field.
#Entity
public class Thing {
#Column(name = "name", unique = true)
private String name;
#ManyToOne
private Owner owner;
}
Example works just fine if new Things are created with unique names. But when different owners want to create things with the same name this approach fails.
Is it possible to set unique constraint to differ records of Things in the database based on the Owners using Hibernate/JPA functionalities (I could not find any) or should I write my own logic and dump the unique from #Column.
Perhaps it could be done with Hibernate Validator? Reading the docs I haven't found much about unique constraints.
You're looking for #UniqueConstraint
http://docs.oracle.com/javaee/5/api/javax/persistence/UniqueConstraint.html

Conditional insert with Spring JPA / Hibernate

I'm working on a project that runs in a clustered environment, where there are many nodes and a single database. The project uses Spring-data-JPA (1.9.0) and Hibernate (5.0.1). I'm having trouble resolving how to prevent duplicate row issues.
For sake of example, here's a simple table
#Entity
#Table(name = "scheduled_updates")
public class ScheduledUpdateData {
public enum UpdateType {
TYPE_A,
TYPE_B
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private UUID id;
#Column(name = "type", nullable = false)
#Enumerated(EnumType.STRING)
private UpdateType type;
#Column(name = "source", nullable = false)
private UUID source;
}
The important part is that there is a UNIQUE(type, source) constraint.
And of course, matching example repository:
#Repository
public class ScheduledUpdateRepository implements JpaRepository<ScheduledUpdateData, UUID> {
ScheduledUpdateData findOneByTypeAndSource(final UpdateType type, final UUID source);
//...
}
The idea for this example is that parts of the system can insert rows to be schedule for something that runs periodically, any number of times between said runs. When whatever that something is actually runs, it doesn't have to worry about operating on the same thing twice.
How can I write a service method that would conditionally insert into this table? A few things I've tried that don't work are:
Find > Act - The service method would use the repository to see if a entry already exists, and then either update the found entry or save a new one as needed. This does not work.
Try insert > Update if fail - The service method would try to insert, catch the exception due to the unique constraint, and then do an update instead. This does not work since the transaction will already be in a rolled-back state and no further operations can be done in it.
Native query with "INSERT INTO ... WHERE NOT EXISTS ..."* - The repository has a new native query:
#Repository
public class ScheduledUpdateRepository implements JpaRepository<ScheduledUpdateData, UUID> {
// ...
#Modifying
#Query(nativeQuery = true, value = "INSERT INTO scheduled_updates (type, source)" +
" SELECT :type, :src" +
" WHERE NOT EXISTS (SELECT * FROM scheduled_updates WHERE type = :type AND source = :src)")
void insertUniquely(#Param("type") final String type, #Param("src") final UUID source);
}
This unfortunately also does not work, as Hibernate appears to perform the SELECT used by the WHERE clause on its own first - which means in the end multiple inserts are tried, causing a unique constraint violation.
I definitely don't know a lot of the finer points of JTA, JPA, or Hibernate. Any suggestions on how insert into tables with unique constraints (beyond just the primary key) across multiple JVMs?
Edit 2016-02-02
With Postgres (2.3) as a database, tried using Isolation level SERIALIZABLE - sadly by itself this still caused constraint violation exceptions.
You are trying to ensure that only 1 node can perform this operation at a time.
The best (or at least most DB-agnostic) way to do this is with a 'lock' table.
This table will have a single row, and will act as a semaphore to ensure serial access.
Make sure that this method is wrapped in a transaction
// this line will block if any other thread already has a lock
// until that thread's transaction commits
Lock lock = entityManager.find(Lock.class, Lock.ID, LockModeType.PESSIMISTIC_WRITE);
// just some change to the row, it doesn't matter what
lock.setDateUpdated(new Timestamp(System.currentTimeMillis()));
entityManager.merge(lock);
entityManager.flush();
// find your entity by unique constraint
// if it exists, update it
// if it doesn't, insert it
Hibernate and its query language offer support for an insert statement. So you can actually write that query with HQL. See here for more information. http://docs.jboss.org/hibernate/orm/5.0/userguide/html_single/Hibernate_User_Guide.html#_hql_syntax_for_insert
It sounds like an upsert case, that can be handled as suggested here.
Find > Act - The service method would use the repository to see if a entry already exists, and then either update the found entry or save a new one as needed. This does not work.
Why does this not work?
Have you considered "optimistic locking"?
These two posts may help:
https://www.baeldung.com/jpa-optimistic-locking
https://www.baeldung.com/java-jpa-transaction-locks

Duplicate entries in Hibernate

I am facing a strange problem in Hibernate. Operating in a multithreaded env, when trying to insert into one of the tables, getting duplicate entries in table. Only the primary key is different, rest all other fields are getting exactly duplicate.
Using Hibernate + Oracle and using Spring - HibernateTemplate object.
Here's the relevant portion of my my BO class and below given code to save the object. Not using any transient fields.
Have checked other posts related to this, but none of them addresses the root cause of the problem. I don't want to introduce any constraints/unique indexes on db table.
#Entity
#Table(name="ADIRECIPIENTINTERACTION")
#Lazy(value = true)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#GenericGenerator(name="recipientInteractionSeq", strategy = "native", parameters =
{ #Parameter(name="sequence", value="SEQiRecipientInteractId")})
public class RecipientInteractionBO extends BusinessObject{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "recipientInteractionSeq", strategy = GenerationType.AUTO)
#Column(name="IRECIPIENTINTERACTIONID")
private long lId; ....
And here's the Code used to save the BO.
-----------------------------------------------------
RecipientInteractionBO recInt = (RecipientInteractionBO) objectPS
.getUniqueResult(detachedCriteria);
if (recInt == null) {
recInt = new RecipientInteractionBO();
....
hibernateTemplateObj.insertObject(recInt);
} else {
...
hibernateTemplateObj.saveOrUpdate(recInt);
}
Please let me know if any other details are required.
Check your data persistence code for possible race conditions for multiple threads. You are checking for the existence of the RecipientInteractionBO which is possibly querying from database. If two threads are running simultaneously, both check for it's existence, since for both it's not there both persist the new entity. You might need to use synchronization to make the process of checking and inserting/updating to be done only for one thread at a single time.

Best way to prevent unique constraint violations with JPA

I have an Keyword and a KeywordType as entities. There are lots of keywords of few types. When trying to persist the second keyword of a type, the unique constraint is violated and the transaction is rolled back. Searching SO i found several possibilies (some of them from different contexts, so I'm not sure of their validity here) - this post and this post advise catching the Exception which would be of no use to me as I end up where I started and still need to somehow persist the keyword.
Same applies to locking proposed for a different situaltion here Custom insert statements as proposed in this and this posts wouldn't work proper I guess, since I'm using Oracle and not MySQL and woulnd like to tie the implementation to Hibernate. A different workaround would be trying to retrieve the type first in the code generating the keywords, and set it on the keyword if found or create a new one if not.
So, what would be the best - most robust, portable (for different databases and persistence providers) and sane approach here?
Thank you.
The involved entities:
public class Keyword {
#Id
#GeneratedValue
private long id;
#Column(name = "VALUE")
private String value;
#ManyToOne
#JoinColumn(name = "TYPE_ID")
private KeywordType type;
...
}
and
#Entity
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = { "TYPE" }) })
public class KeywordType {
#Id
#GeneratedValue
private long id;
#Column(name = "TYPE")
private String type;
...
}
Your last solution is the right one, IMO. Search for the keyword type, and if not found, create it.
Catching the exception is not a good option because
it's hard to know which exception to catch and make your code portable across JPA and DB engines
The JPA engine will be in an undetermined state after such an exception, and you should always rollback in this case.
Note however that with this technique, you might still have two transactions searching for the same type in parallel, and then try to insert it in parallel. One of the transaction will rollback, but it will be much less frequent.
If you're using EJB 3.1 and you don't mind serializing this operation, a singleton bean using container managed concurrency can solve the problem.
#Singleton
#ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
public class KeywordTypeManager
{
#Lock(LockType.WRITE)
public void upsert(KeywordType keywordType)
{
// Only one thread can execute this at a time.
// Your implementation here:
// ...
}
#Inject
private KeywordTypeDao keywordTypeDao;
}
I would go for this option:
A different workaround would be trying
to retrieve the type first in the code
generating the keywords, and set it on
the keyword if found or create a new
one if not.

Categories

Resources