How to access web layer(Controllers) from the DAO Layer? - java

I had to cancel the running queries. In my DAO I set the Entity.session in the ServletContext when I start the query and remove it when the query is finished. Then check in the Controller if the session is present in the ServletContext, if it is present then I cancel the query by calling the session.cancelQuery() from the session object in the ServletContext.
This was working fine in my dev environment, but in my pre-prod testing the entire code does not run inside the tomcat container. The web part runs in tomcat whereas the data layer runs a java application. hence I could not find the ServletContext in the DAO class and it gave me a ClassNotFound Exception
So I decoupled the web layer in the DAO. Now I set the hibernate.session in the controller itself when it calls the DAO calculate(). But this created a problem, now the session exists even if there are no calculations going on and in actual there are some post or precalculations. And my mechanism to cancel the query doesn't work.
So what I need is a way to access the Controller from the DAO to set the session. I could have used a static method in the Controller and then set the session from it but I think this is again not a good practice.
DAO initial Code:
public calculate(){
Session session = mEntityManager.unwrap(Session.class);
//pre possing
if(session != null)
{
mContext.setAttribute(""+view.getId(), session);
}
List<Object[]> results = query.getResultList();
mContext.removeAttribute(""+view.getId());
//post processing
}
Decoupled DAO code:
The method getSession() is called from the controller before the calculate method is called in the controller. And then when the user requests a cancel from the UI the cancel method is called in the controller.
public Session getSession()
{
Session session = mEntityManager.unwrap(Session.class);
return session;
}
public calculate(){
//pre possing
List<Object[]> results = query.getResultList();
//post processing
}
Controller:
#RequestMapping
public WebServiceResponse cancel(HttpServletRequest request)
{
if(mContext.getAttribute(id) != null)
((Session)mContext.getAttribute(id)).cancelQuery();
}

It seems you have convoluted control flow because you are not properly separating detection of a problem from handling the problem: you detect a problem in the data layer but need to handle it in the presentation layer.
Consider using exceptions. Have the data layer throw an exception when it detects a problem. The presentation layer can handle problems in the data layer by catching exceptions.

Related

Achieving transactional integrity using two service operations in a rest controller

I have a question regarding the transactional boundaries in REST-Controllers using Java/Jakarta EE and JAX-RS.
Given the following controller, when a user is created in the #POST method, first the user is created using a dedicated service. Afterwards, the user´s permissions are stored using a different service.
Both services wrap incoming calls in a container managed transaction.
#Path("/users")
public class UserController {
#Inject
private UserService userService;
#Inject
private PermissionService permissionService;
#POST
public UserDto createUser(UserDto userDto) {
User user = mapToUser(userDto);
User createdUser = userService.create(user);
Permissions perms = mapToPermissions(userDto);
permissionService.createPermissions(createdUser.getId());
return mapToUserDto(createdUser);
}
}
From my point of view, there exists a transactional boundary around userService.create() and a second one around permissionService.createPermissions(). Can you confirm that this is true?
Secondly, if the previous statement is true, and the applications shuts down mid processing, the users permissions might not be persisted. Is this appraoch considered bad practise and should both operations be performed in a single transaction (or in the REST-paradigm under different ressources / endpoints)?
You are correct, there are two transactions, so the first could succeed while the second fails.
A common practice is using a Service Façade pattern. A coarse grained service that sets the transactions boundaries, and call the services to achieve the result.

Data not saved in db when the time #PostPersist got called

I need to send a request to other microService once the object got created in the database. I only send the object id so other microService needs to call the db again for the info with bunch of other stuff.
But, when the other microService try to lookup for the record using the received id it cannot find the saved record in the database.
I tried debug seems like record does not persist even though #postPersist got called.
It will be saved after #PostPersist got executed.
Has anyone could give a workaround for this. I really need to query the database again as this is a custom requirement. I use mysql and spring boot
public class EmployeeListener {
#PostPersist
public void sendData(Employee employee){
Long id = employee.getEmployeeId();
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange("http://localhost:8081/service/employee"+id, HttpMethod.POST, null, String.class);
}
}
#Entity
#EntityListeners(EmployeeListener.class)
public class Employee {
//
}
The problem is that JPA lifecycle events happen in the same transaction as your save operation, but the lookup, since it happens with a different server must only happen after your transaction is closed.
I therefore recommend the following setup: Gather the ids that need informing in a Collection and then when the transaction is completed send the data.
If you want to have the send operation and save operation in one method, the [TransactionTemplate][1] might be nicer to use than transaction management by annotation.
You also might consider Domain Events. Note that they trigger only when save is actually called. The benefit of these events is that they get published using a ApplicationEventPublisher for which listeners are Spring Beans so you may inject whatever bean you find helpful. They still need a way to break out of the transaction as described above
#PostPersist annotated method is called within the same transaction and the default flash mode is AUTO, that's why you don't see the record in the database. You need to force a flush:
#Component
public class EmployeeListener {
#PersistenceContext
private EntityManager entityManager;
#PostPersist
public void sendData(Employee employee){
// Send it to database
entityManager.flush();
Long id = employee.getEmployeeId();
RestTemplate restTemplate = new RestTemplate();
restTemplate.exchange("http://localhost:8081/service/employee"+id, HttpMethod.POST, null, String.class);
}
}
Notice that EmployeeListener needs to be a Spring managed bean.

How to prevent concurrent call in Spring MVC

Deal all, i am facing one problem in spring mvc. From one of our external system say payment gateway will call us [/getRespFromExternal] after their transaction completed, unfortunately they are calling us more than one time at time.
In the method getRespFromExternal, we are calling internal webservice to process payment acknowledgement, before WS call completed itself i get another duplicate call from external system. So i get payInd is null in payResponse method in controller. after first call [WS call] finished then /payResponse from controller is not even calling . Sorry for my english. Please advice me.
Service Method called by External System:
#RequestMapping(value="/getRespFromExternal",method=RequestMethod.POST)
public String getRespFromExternal(HttpServletRequest httpRequest,HttpServletResponse httpResponse,HttpSession session){
if (httpRequest != null && session.getAttribute("extRespId") == null) {
session.setAttribute("extRespId", httpRequest.getParameter("tranID"));
// internal web service call
callInternalWS(); // to process payment
before processing above WS method i get another call from external[/getRespFromExternal] for second time;
session.setAttribute("payInd", "Y");
return "redirect:/payResponse/";
}
return "redirect:/payResponse/";
}
Controller Method :
#RequestMapping(value = "/payResponse/", method = RequestMethod.GET)
public ModelAndView payResponse(HttpServletRequest request, HttpSession session) {
String payInd = (String) session.getAttribute("payInd");
System.out.println("payInd --> "+payInd);
if (payInd != null && payInd.trim().equalsIgnoreCase("Y") ) {
}
}
If you are getting multiple calls to your service with the same transaction if, that should be treated as an error.
I assume that you are using a transactional database to record the payments.
If so, then you could make the transaction id a unique key in the database. If the SQL insert or update to record the transaction fails due to a duplicate key, redirect to a different URL to report the error.

Struts 2 + Spring put a managed spring bean in session

Consider an Struts 2 + Spring 4 project.
For each login the User object is put in session. As a very simple action it will look
public class LoginProcess implements ServletRequestAware {
#Inject
private AuthenticationServices authenticationServices;
public String execute() {
//The login method makes a new User and fills its setters
User newUser = authenticationServices.login(....);
getServletRequest().getSession().setAttribute("USER_SESSION", user);
}
}
As we manually make a new User object so it is not managed spring bean, and we can't use spring features in User class: #Inject , #Value ,...
I tried to change user as:
#Named
#Scope(value="session")
public class User { ...
#Inject
private AccountServices accountServices;
}
and inject the User in instead of calling new User, but I get the error:
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.SessionScope.get(SessionScope.java:91)
Well although it describes the error, but I can not find how can I fix it, and I am not sure if this is the correct way at all. It seems that I can only use spring session scope been when I am using spring mvc
Any comments ?!
Why I need this ?! (Simplified situation)
The user object has a getAccounts() methods which get all user accounts. Getting user accounts is an expensive operation, and it is possible that a user does not require its accounts during its login.
So, instead of get user accounts as soon as user logs in, we let the get method get user accounts if it does not have it:
public class User() {
private Accounts accounts;
#Inject
private AccountServices accountServices;
Accounts getAccounts() {
if (accounts == null) {
accounts = accountServices.getUserAccountsFromDB(...)
}
return accounts;
}
Don't create a new instance of User by yourself, instead get a bean from Spring context.
For example you can achieve it by implementing ApplicationContextAware interface and calling one of getBean methods.
User user = applicationContext.getBean(User.class);
// populate user and put it into session
In that way it is a Spring managed bean an all required properties should be injected.
BUT consider changing your User to a simple POJO and moving all business logic (such as fetching users accounts) to some more appropriate place, in that way your model layer will be cleaner and easily testable.

Hibernate multitenancy: change tenant in session

We're developing a SaaS solution for several consumers. This solution is based on Spring, Wicket and Hibernate. Our
database contains data from several customers. We've decided to model the database as follows:
public
Shared data between all customers, for example user accounts as we do not know which customer a user belongs to
customer_1
customer_2
...
To work with this setup we use a multi-tenancy setup with the following TenantIdentifierResolver:
public class TenantProviderImpl implements CurrentTenantIdentifierResolver {
private static final ThreadLocal<String> tenant = new ThreadLocal<>();
public static void setTenant(String tenant){
TenantProviderImpl.tenant.set(tenant);
}
#Override
public String resolveCurrentTenantIdentifier() {
return tenant.get();
}
#Override
public boolean validateExistingCurrentSessions() {
return false;
}
/**
* Initialize a tenant by storing the tenant identifier in both the HTTP session and the ThreadLocal
*
* #param String tenant Tenant identifier to be stored
*/
public static void initTenant(String tenant) {
HttpServletRequest req = ((ServletWebRequest) RequestCycle.get().getRequest()).getContainerRequest();
req.getSession().setAttribute("tenant", tenant);
TenantProviderImpl.setTenant(tenant);
}
}
The initTenant method is called by a servlet filter for every request. This filter is processed before a connection
is opened to the database.
We've also implemented a AbstractDataSourceBasedMultiTenantConnectionProviderImpl which is set as our
hibernate.multi_tenant_connection_provider. It issues a SET search_path query before every request. This works like charm for requests passing through the servlet filter described above.
And now for our real problem: We've got some entrypoints into our application which do not pass the servlet filter,
for instance some SOAP-endpoints. There are also timed jobs that are executed which do not pass the servlet filter.
This proves to be a problem.
The Job/Endpoint receives a value somehow which can be used to identify which customer should be associated with the
Job/Endpoint-request. This unique value is often mapped in our public database schema. Thus, we need to query the
database before we know which customer is associated. Spring therefore initializes a complete Hibernate session. This
session has our default tenant ID and is not mapped to a specific customer. However, after we've resolved the unique
value to a customer we want the session to change the tenant identifier. This seems to not be supported though, there
is no such thing as a HibernateSession.setTenantIdentifier(String) whereas there is a
SharedSessionContract.getTenantIdentifier().
We thought we had a solution in the following method:
org.hibernate.SessionFactory sessionFactory = getSessionFactory();
org.hibernate.Session session = null;
try
{
session = getSession();
if (session != null)
{
if(session.isDirty())
{
session.flush();
}
if(!session.getTransaction().wasCommitted())
{
session.getTransaction().commit();
}
session.disconnect();
session.close();
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
}
catch (HibernateException e)
{
// NO-OP, apparently there was no session yet
}
TenantProviderImpl.setTenant(tenant);
session = sessionFactory.openSession();
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
return session;
This method however does not seem to work in the context of a Job/Endpoint and leads to HibernateException such as
"Session is closed!" or "Transaction not succesfully started".
We're a bit lost as we've been trying to find a solution for quite a while now. Is there something we've misunderstood?
Something we've misinterpreted? How can we fix the problem above?
Recap: HibernateSession-s not created by a user request but rather by a timed job or such do not pass our servlet
filter and thus have no associated tenant identifier before the Hibernate session is started. They have unique values
which we can translate to a tenant identifier by querying the database though. How can we tell an existing Hibernate
session to alter it's tenant identifier and thus issue a new SET search_path statement?
We've never found a true solution for this problem, but chimmi linked to a Jira-ticket were others have requested such a feature: https://hibernate.atlassian.net/browse/HHH-9766
As per this ticket, the behavior we want is currently unsupported. We've found a workaround though, as the number of times we actually want to use this feature is limited it is feasible for us to run these operations in separate threads using the default java concurrency implementation.
By running the operation in a separate thread, a new session is created (as the session is threadbound). It is very important for us to set the tenant to a variable shared across threads. For this we have a static variable in the CurrentTenantIdentifierResolver.
For running an operation in a separate thread, we implement a Callable. These callables are implemented as Spring-beans with scope prototype so a new instance is created for each time it is requested (autowired). We've implemented our own abstract implementation of a Callable which finalizes the call()-method defined by the Callable interface, and the implementation starts a new HibernateSession. The code looks somewhat like this:
public abstract class OurCallable<TYPE> implements Callable<TYPE> {
private final String tenantId;
#Autowired
private SessionFactory sessionFactory;
// More fields here
public OurCallable(String tenantId) {
this.tenantId = tenantId;
}
#Override
public final TYPE call() throws Exception {
TenantProvider.setTenant(tenantId);
startSession();
try {
return callInternal();
} finally {
stopSession();
}
}
protected abstract TYPE callInternal();
private void startSession(){
// Implementation skipped for clarity
}
private void stopSession(){
// Implementation skipped for clarity
}
}
Another workaround I've found thanks to #bas-dalenoord comment regarding OpenSessionInViewFilter/OpenEntityManagerInViewInterceptor which led me to this direction, is to disable this interceptor.
This can be achieved easily by setting spring.jpa.open-in-view=false either in the application.properties or environment-variable.
OpenEntityManagerInViewInterceptor binds a JPA EntityManager to the thread for the entire processing of the request and in my case it's redundant.
Another workaround is to break the request that needs to make DB calls on behalf of 2 different tenants into 2 separate requests.
First the client ask for his associated tenant in the system, and then creates a new request with the given tenant as a parameter. IMO, until (and if) the feature will be supported, it's a relatively clean alternative.

Categories

Resources