📜 ⬆️ ⬇️

Hibernate envers. Substitution ID of the user who made the change

image

Good afternoon, dear habrovchane. This is my first article, please do not swear much.

Much has already been written about listening in Hibernate. I want to talk about the solution of a not quite standard task - writing to the revision table the ID of any user assigned immediately before the operation of writing an entity to the database. The standard solution proposed in the official documentation is to use the user ID stored in the session bean. But it is possible that the user ID must be changed. Example: a user performs operations through interaction with a telephony server using DTMF signals. In this case, the session does not need to create. I have been looking for a solution on the Internet for a long time, but I haven’t found anything, so I offer you my version. Perhaps some of the newbies, like me, it will be useful.

Reading the documentation, I realized that listening to Hibernate is based on interceptors. This means that the thread that updates the entity in the database is responsible for listening - this is already something.
')
Let's try to benefit from it. We create a stateless component in which a static map with pairs will be stored: stream ID — user ID. The start method adds the user ID (passed by the parameter) and the current thread ID to the map, then launches a method in the new transaction that performs the necessary actions, waits for the method (transaction) to end, and removes the stream ID and user ID from the map.

@Stateless @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public class FakeOwnerTransaction { @Inject private Provider<FakeOwnerTransaction> providerFakeOwnerNewTransaction; private static ThreadLocal<Long> threadOwnerID = new ThreadLocal<>() @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void newCMT(Runnable action) { action.run(); } public void start(Long personID, Runnable action) { threadOwnerID.set(personID); try { providerFakeOwnerNewTransaction.get().newCMT(action); } finally { threadOwnerID.remove(); } } public static Long getFakeChanger() { return threadOwnerID.get(); } } 

Now let's see what the RevisionListener implementation will look like. If the current thread is associated with a user ID, use this ID, otherwise we take the user ID from the session component UserManager.

 public class Audition implements RevisionListener { @Override public void newRevision(Revinfo revinfo) { Long personID = FakeOwnerTransaction.getFakeChanger(); if (personID == null) { UserManager userManager = SystemUtils.lookup(JNDI_NAME_PREFIX, UserManager.class); personID = userManager.getPersonID(); } revinfo.setPersonID(personID); } } 

And finally, we will try to make a change in the database using the specified user ID.

 FakeOwnerTransaction fakeOwnerTransaction = SystemUtils.lookup(JNDI_NAME_PREFIX, FakeOwnerTransaction.class); fakeOwnerTransaction.start(getPersonID(), new Runnable() { @Override public void run() { Dao dao = SystemUtils.lookup(JNDI_NAME_PREFIX, Dao.class); dao.add(new Person(“Smirmov”)); } }); 


SystemUtils class
 public class SystemUtils { public static <T> T lookup(String jndiNamePrefix, Class<T> clazz) { String jndiName = jndiNamePrefix; jndiName += clazz.getSimpleName() + "!" + clazz.getName(); try { return (T) new InitialContext().lookup(jndiName); } catch (Exception e) { CoreSharedUtils.getLogger().severe("Error. Bean '" + jndiName + "' not found!"); e.printStackTrace(); } return null; } … } 


Important notes
Today, one intelligent person with Habr, who for some reason cannot write comments, noted an important problem that should be paid attention to.
The thread can be terminated from outside the JVM, in which case the block finaly will not be executed. Hypothetically, this may lead to a memory leak, but this option suits me.


I have it all. If someone has more interesting solutions or criticism, welcome to the comments.

Source: https://habr.com/ru/post/275431/


All Articles