Spring - get used TransactionManager - java

I have two transaction managers and was curious if there is some possibility to get the one that has been used.
To be more concrete, how could underlyingMethod(..) find out which transactionManager was used (without sending it an additional parameter "transactionManagerName/Ref"):
#Transactional("transactionManager1")
public void transactionFromFirstTM() {
someClass.underlyingMethod()
}
#Transactional("transactionManager2")
public void transactionFromSecondTM() {
someClass.underlyingMethod()
}
?
ok I have used this to get the hibernate Session from actual transaction manager:
protected Session getSession() {
Map<Object, Object> resourceMap = TransactionSynchronizationManager.getResourceMap();
Session session = null;
for (Object value : resourceMap.values()) {
if (value instanceof SessionHolder) {
session = ((SessionHolder) value).getSession();
break;
}
}
return session;
}

I don't think you can, but you shouldn't do anything with the transaction manager. Some actions on the current transaction are available in TransactionSynchronizationManager
Another useful class is the TransactionAspectUtils. But not that both are meant to be used internally, and you should not rely on them in many places in your code.

Related

Intercept repository method calls in Spring Data, refining the query on the fly

Say I've got a few interfaces extending CRUDRepositor. There are methods in there like findByField. Some of these methods should only return entities that belong to a group of entities to which the user has access (the group is a column in the database, so it's a field that's defined for most entities). I want to achieve this by allowing the use of annotations (like #Protected) on the repository methods, and then when these methods are called instead of calling findByField a method findByFieldAndGroup is called behind the scenes. With the use of AOP (which intercepts methods annotated with my #Protected tag) the group can be assigned before the method is effectively executed.
public interface MyRepository extends CRUDRepository<MyEntity,long> {
#Protected
Optional<MyEntity> findById(Long id); // Should become findByIdAndGroup(Long id, String group) behind the scenes
#Protected
Collection<MyEntity> findAll();
}
Is there a way to achieve this? In the worst case I either add all the methods manually, or completely switch to a query by example approach (where you can more easily add the group dynamically) or generate methods with a Java agent using ASM (manipulating the bytecode) ... but these are much less practical approaches which demand a good deal of refactoring.
Edit : found these relevant questions Spring data jpa - modifying query before execution
Spring Data JPA and spring-security: filter on database level (especially for paging)
Other relevant references include this ticket on GitHub (no progress, only a sort-of-solution with QueryDSL which precludes the use of queries based on method names) and this thread.
You can use filters, a specific Hibernate feature, for this problem.
The idea is the following.
First, you need to annotate your entity with the different filters you want to apply, in your case, something like:
#Entity
//...
#Filters({
#Filter(name="filterByGroup", condition="group_id = :group_id")
})
public class MyEntity implements Serializable {
// ...
}
Then, you need access to the underlying EntityManager because you need to interact with the associated Hibernate Session. You have several ways to do this. For example, you can define a custom transaction manager for the task, something like:
public class FilterAwareJpaTransactionManager extends JpaTransactionManager {
#Override
protected EntityManager createEntityManagerForTransaction() {
final EntityManager entityManager = super.createEntityManagerForTransaction();
// Get access to the underlying Session object
final Session session = entityManager.unwrap(Session.class);
// Enable filter
try{
this.enableFilterByGroup(session);
}catch (Throwable t){
// Handle exception as you consider appropriate
t.printStackTrace();
}
return entityManager;
}
private void enableFilterByGroup(final Session session){
final String group = this.getGroup();
if (group == null) {
// Consider logging the problem
return;
}
session
.enableFilter("filterByGroup")
.setParameter("group_id", group)
;
}
private String getGroup() {
// You need access to the user information. For instance, in the case of Spring Security you can try:
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
// Your user type
MyUser user = (MyUser)authentication.getPrincipal();
String group = user.getGroup();
return group;
}
}
Then, register this TransationManager in your database configuration instead of the default JpaTransactionManager:
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new FilterAwareJpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory());
return transactionManager;
}
You can also have access to the EntityManager and associated Session by creating a custom JpaRepository or by injecting #PersistenceContext in your beans, but I think the above-mentioned approach is the simpler one although it has the drawback of being always applied.

How to maintain Hibernate session opened per request?

As far as I know session per request means we should use only one hibernate session per servlet request, not one session per transaction.
Consider this helper class:
public class HibernateUtil {
private static SessionFactory sessionFactory;
// this is called by the servlet context listener
public static void buildSessionFactory() {
// build session factory
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
// check if the entity user already exist in the database
public static User getUser(String email) {
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
User user = (User)session.createQuery("from User c where c.emailAddress like :email").setParameter("email", email).uniqueResult();
session.getTransaction().commit();
return user;
}
// if getUser returns null, insert this user in the database
public static void insertUser(User user) {
Session session = getSessionFactory().getCurrentSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
}
}
Now each method has it's own session object, but when the register servlet gets called I have to do this:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
User user = // initialise user
if (getUser(request.getParameter("email")) == null) { // session
insertUser(user); // another session
request.setAttribute("user", user);
request.getRequestDispatcher("homepage.jsp").forward(request, response);
} else {
request.setAttribute("message", "This account already exist");
populateFields(request);
request.getRequestDispatcher("register.jsp").forward(request, response);
}
}
Now, my register servlet effectively used two sessions, vilating the pattern. I've thought of possible ways to fix:
Set a global Session object in the hibernateUtil class, then prevent getUser from committing the transaction. I think this means every method can now use this session, even in multiple request if the transaction is not committed. My problem is what is my only operation is to get a user and nothing more?
Let getUser return the session and pass it to insertUser, I don't know, I'm not sure if it's good to pass session objects around.
Just get rid of the helper class and write the code directly in the servlet and remove the helper class. I really think it would simplify the management of sessions, but I might not be able to reuse some transactions.
What should I do? And are there better code that does what I want to do?
The best option in this case is to have a design like this:
Have an abstract Dao that will have a Session session as field and will allow the injection of this via constructor or setter (up to you). All your Daos will be subclasses of this class.
The service classes can create a Session session in their methods or receive it as parameter. In this way, the service may inject the session to the necessary daos used in its methods. This also enables the service to start and commit/rollback a transaction.
In case you want/need that services should be unaware of opening/closing hibernate sessions, then you may create a wrapper for the interfaces that will create a session, inject it to a service, and then execute the proper methods of the service(s). After executing the method, this wrapper will commit/rollback the operations done in the session.
If you use a framework like Spring that provides integration with Hibernate, you will see that they have a similar design:
Dao classes must allow the injection of HibernateTemplate via constructor parameter or setter.
Services will use dao interfaces as needed.
Use of <transactional> or #Transactional will create a proxy class that wraps your service to open the session, execute the proper method of the service, and commit/rollback the operations, depending on the configuration of the transaction management done there.
You can implement a filter that creates the session and then stores it in the request, so you can reuse it anywhere you have access to the request object:
public class SessionPerRequestFilter implements Filter {
public static final String SESSION_ATTRIBUTE_NAME = "myapp.hibernate.session";
private static SessionFactory sessionFactory;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// Locate the session on the request object
Session session = (Session) request.getAttribute(SESSION_ATTRIBUTE_NAME);
if(session == null) {
// Create a new session
session = HibernateUtil.getSessionFactory().openSession();
request.setAttribute(SESSION_ATTRIBUTE_NAME, session);
}
try {
// Continue the filter chain
chain.doFilter(request, response);
}
finally {
// Close the session
session.close();
}
}
#Override
public void destroy() {
}
}
In your servlets you then get the session by
Session session = (Session) request.getAttribute(SessionPerRequestFilter.SESSION_ATTRIBUTE_NAME);
Make sure you only close your transactions and not the session in your helper classes though.

How to get the transaction Status from HibernateTransactionManager

I am trying to upgrade my spring transaction manager from JtaTransactionManager to HibernateTransactionManager. In JTA TransactionManager we have one method which gives the status of current transaction. Based on the status we are doing some operations. The implementation is as follows :
private void checkTransactionStatus(TransactionStatus status){
if(status instanceof DefaultTransactionStatus) {
DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) status;
if(transactionStatus.getTransaction() instanceof JtaTransactionObject){
JtaTransactionObject txObject = (JtaTransactionObject) transactionStatus.getTransaction();
int jtaStatus;
try {
jtaStatus = txObject.getUserTransaction().getStatus();
if(jtaStatus==Status.STATUS_MARKED_ROLLBACK){
// logic heare
}
} catch (SystemException e) {}
}
}
}
I want to replace this method with HibernateTransactionManager specific code. I analyzed and found that, HibernateTransactionManager is using HibernateTransactionObject as transaction object. But, unfortunately it's a private inner class that I can't use to get the status. Then I tried to use the parent class JdbcTransactionObjectSupport. But, I don't know how to get the status from this parent class object.
private void checkTransactionStatus(TransactionStatus status){
if(status instanceof DefaultTransactionStatus) {
DefaultTransactionStatus transactionStatus = (DefaultTransactionStatus) status;
if(transactionStatus.getTransaction() instanceof JdbcTransactionObjectSupport){
JdbcTransactionObjectSupport txObject = (JdbcTransactionObjectSupport) transactionStatus.getTransaction();
//how to get the current status ?
}
}
}
Spring has a mechanism for receiving callbacks. You can implement the TransactionSynchronization interface (or easier extend the TransactionSynchronizationAdapter). You probably want to implement the afterCompletion(int) method and put your logic in there.
public class MyTxCallback extends TransactionSynchronizationAdapter {
public void afterCompletion(int status) {
if (status==STATUS_ROLLED_BACK) {
//logic here.
}
}
}
You can then bind that to the transaction by calling the TransactionSynchronizationManager when a transaction is started. Now when the transaction is done the method will be called and you can do your logic (regardless of the underlying transactional resource used).
If you use HibernateTransactionManager you can get the current transaction state from the Hibernate Session:
LocalStatus status = session.getTransaction().getLocalStatus();
and the LocalStatus has the following states:
public enum LocalStatus {
/**
* The local transaction has not yet been begun
*/
NOT_ACTIVE,
/**
* The local transaction has been begun, but not yet completed.
*/
ACTIVE,
/**
* The local transaction has been competed successfully.
*/
COMMITTED,
/**
* The local transaction has been rolled back.
*/
ROLLED_BACK,
/**
* The local transaction attempted to commit, but failed.
*/
FAILED_COMMIT
}

Injecting login session using Dagger

I'm adapting my project to utilize DI - Dagger in particular. I have no problems injecting classes, but how do you 'inject' a runtime generated variable?
Say, I want to store user's login session object that contains data such as api-token, loggedUsername and whatnot inside. This object is generated at runtime when the user has successfully logged in, in another class responsible for doing so.
As an illustration, what I want is to simply do something like this:
public class FooPresenter {
...
Session mSession;
#Inject FooPresenter(Session session, ...) {
mSession = session;
...
}
...
}
Thanks in advance!
I ended up declaring a method to obtain my session, which I kept as a String in SharedPreferences after a successful login, in a Dagger module class.
That way, I could just inject the session anywhere it's needed.
Here's my SessionDomainModule:
#Module(
library = true,
complete = false)
public class SessionDomainModule {
// TODO: Rethink this implementation.
#Provides
public Session provideSession(Application application) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(application);
String sessionJString = preferences.getString(AppConfig.PREF_KEY_CURRENT_SESSION, "");
Session session;
if(!"".equals(sessionJString)) {
return new Gson().fromJson(sessionJString, Session.class);
}
return null;
}
...
}
By doing that, I could achieve what I wanted and codes in the like of those on my question could finally work.
You can have a SessionModule which keeps a reference to your current Session:
#Module
class SessionModule {
#Nullable
private Session mSession;
#Provides Session provideSession() {
return mSession;
}
void setSession(Session session) {
mSession = session;
}
}
You will need a reference to this SessionModule however to update the session object.
Another way is to create a (singleton) SessionProvider, which does nothing more than holding the Session:
#Singleton
class SessionProvider {
Session session;
#Inject
SessionProvider() {
}
}
Then, instead of injecting the Session instance, you would inject the SessionProvider, from which you can retrieve / update the Session.

Hibernate Save Object to Multiple Sessions

I am attempting to write to multiple databases using hibernate. I have encapsulated write and read/write sessions within a single session object. However, when I go to save I get a lot of errors that the objects are already associated with another session: "Illegal attempt to associate a collection with two open sessions"
Here is my code:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
writeOnlySession.saveOrUpdate(arg0);
}
}
I have tried evicting the object and flushing; however, that causes problems with "Row was updated or deleted by another transaction"... even though both sessions point to different databases.
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
readWriteSession.flush();
readWriteSession.evict(arg0);
writeOnlySession.saveOrUpdate(arg0);
writeOnlySession.flush();
writeOnlySession.evict(arg0);
}
}
In addition to the above, I have also attempted using the replicate functionality of hibernate. This was also unsuccessful without errors.
Has anyone successfully saved an object to two databases that have the same schema?
The saveOrUpdate tries to reattach a given Entity to the current running Session, so Proxies (LAZY associations) are bound to the Hibernate Session. Try using merge instead of saveOrUpdate, because merge simply copies a detached entity state to a newly retrieved managed entity. This way, the supplied arguments never gets attached to a Session.
Another problem is Transaction Management. If you use Thread-bound Transaction, then you need two explicit transactions if you want to update two DataSources from the same Thread.
Try to set the transaction boundaries explicitly too:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
Transaction readWriteSessionTx = null;
try {
readWriteSessionTx = readWriteSession.beginTransaction();
readWriteSession.merge(arg0);
readWriteSessionTx.commit();
} catch (RuntimeException e) {
if ( readWriteSessionTx != null && readWriteSessionTx.isActive() )
readWriteSessionTx.rollback();
throw e;
}
Transaction writeOnlySessionTx = null;
try {
writeOnlySessionTx = writeOnlySession.beginTransaction();
writeOnlySession.merge(arg0);
writeOnlySessionTx.commit();
} catch (RuntimeException e) {
if ( writeOnlySessionTx != null && writeOnlySessionTx.isActive() )
writeOnlySessionTx.rollback();
throw e;
}
}
}
As mentioned in other answers, if you are using Session then you probably need to separate the 2 updates and in two different transactions. The detached instance of entity (after evict) should be able to be reused in the second update operation.
Another approach is to use StatelessSession like this (I tried a simple program so had to handle the transactions. I assume you have to handle the transactions differently)
public static void main(final String[] args) throws Exception {
final StatelessSession session1 = HibernateUtil.getReadOnlySessionFactory().openStatelessSession();
final StatelessSession session2 = HibernateUtil.getReadWriteSessionFactory().openStatelessSession();
try {
Transaction transaction1 = session1.beginTransaction();
Transaction transaction2 = session2.beginTransaction();
ErrorLogEntity entity = (ErrorLogEntity) session1.get(ErrorLogEntity.class, 1);
entity.setArea("test");
session1.update(entity);
session2.update(entity);
transaction1.commit();
transaction2.commit();
System.out.println("Entry details: " + entity);
} finally {
session1.close();
session2.close();
HibernateUtil.getReadOnlySessionFactory().close();
HibernateUtil.getReadWriteSessionFactory().close();
}
}
The issue with StatelessSession is that it does not use any cache and does not support cascading of associated objects. You need to handle that manually.
Yeah,
The problem is exactly what it's telling you. The way to successfully achieve this is to treat it like 2 different things with 2 different commits.
Create a composite Dao. In it you have a
Collection<Dao>
Each of those Dao in the collection is just an instance of your existing code configured for 2 different data sources. Then, in your composite dao, when you call save, you actually independently save to both.
Out-of-band you said you it's best effort. So, that's easy enough. Use spring-retry to create a point cut around your individual dao save methods so that they try a few times. Eventually give up.
public interface Dao<T> {
void save(T type);
}
Create new instances of this using a applicationContext.xml where each instance points to a different database. While you're in there use spring-retry to play a retry point-cut around your save method. Go to the bottom for the application context example.
public class RealDao<T> implements Dao<T> {
#Autowired
private Session session;
#Override
public void save(T type) {
// save to DB here
}
}
The composite
public class CompositeDao<T> implements Dao<T> {
// these instances are actually of type RealDao<T>
private Set<Dao<T>> delegates;
public CompositeDao(Dao ... daos) {
this.delegates = new LinkedHashSet<>(Arrays.asList(daos));
}
#Override
public void save(T stuff) {
for (Dao<T> delegate : delegates) {
try {
delegate.save(stuff);
} catch (Exception e) {
// skip it. Best effort
}
}
}
}
Each 'stuff' is saved in it's own seperate session or not. As the session is on the 'RealDao' instances, then you know that, by the time the first completes it's totally saved or failed. Hibernate might want you to have a different ID for then so that hash/equals are different but I don't think so.

Categories

Resources