I want to create session factory which is shared by all system database users. As session factory is heavy component it is not right to create it every time user login to application.
My scenario is that, I have an application which is login by system database users i.e. Mysql database users not by application users. So I need a session factory which can be created only once and used by all system database users.
Is it possible to do that?
Yes, it is possible. you can create a class which have static object of SessionFactory. suppose this class have an static block so that it will execute only once at the time of when the class is loaded into JVM. In that static block you have to initialize SessionFactory and when you want to use sessionFactory you can call getter of sessionFactory object.
public final class SessionFactoryHelper {
private static SessionFactory sysDBUserSessionFactory;
static {
try {
// initialize the sysDBUserSessionFactory object.
} catch (Exception e) {
//exception handling
}
}
public static SessionFactory getsysDBUserSessionFactory() {
return sysDBUserSessionFactory;
}
}
Whenever you want sessionFactory for system database users use :
SessionFactory sessionFactory = SessionFactoryHelper.getsysDBUserSessionFactory();
Related
I have the following scenario:
I have one controller which based on a path variable calls a different service.
In every service there is a transactional method where some import logic is happening(call one external api, get a csv file, parse it, convert it to entity and save it in database).
Additionally in every service I want to keep statistics of how many entities will be updated, inserted and deleted. For that reason I am using the org.hibernate.SessionFactory . One example of how I am using that is:
#Service
#Slf4j
public class MarketReportImporterImpl extends Support implements MarketReportImporter {
#Override
#Transactional
public void importMarketReports(ImporterLog importerLog) {
try {
String export = getCsvFile();
Session session = getCurrentSessionAndClearSessionFactoryStatistics();
// parse the csv and save the entities
flushSession(session);
setSuccessfulImport(session, importerLog);
} catch (Exception e) {
log.error("Failed to import market reports. Unable to parse export", e);
getTelemetryClient().trackException(e);
importerLogService.setFailedImport(importerLog, e.getMessage());
}
}
and the methods getCurrentSessionAndClearSessionFactoryStatistics() and setSuccessfulImport(session, importerLog); are in the Support class:
#Component
public abstract class Support {
private final ImporterLogService importerLogService;
#PersistenceContext
private EntityManager entityManager;
public Support(ImporterLogService importerLogService) {
this.importerLogService = importerLogService;
}
public void flushSession(Session session) {
session.flush();
}
public void setSuccessfulImport(Session session, ImporterLog importerLog) {
Statistics statistics = session.getSessionFactory().getStatistics();
int entityInsertCount = (int) statistics.getEntityInsertCount();
int entityDeleteCount = (int) statistics.getEntityDeleteCount();
int entityUpdateCount = (int) statistics.getEntityUpdateCount();
importerLogService.setSuccessfulImport(importerLog, entityUpdateCount, entityDeleteCount, entityInsertCount);
}
public Session getCurrentSessionAndClearSessionFactoryStatistics() {
Session session = entityManager.unwrap(Session.class);
SessionFactory sessionFactory = session.getSessionFactory();
sessionFactory.getStatistics().clear();
return session;
}
This works perfectly fine when calling it for one importer. But If from the frontend I am calling quickly two importers (meaning two threads run in parallel) there will be mix in the results. The session.getSessionFactory().getStatistics(); will have mix results from the first importer and from the second importer and I want to have a clear result only for the current session. For example service A and service B are running in parallel and in service A I am saving entities of type aa and in service B of type bb. In each service I want to know how many entities are saved, updated or deleted meaning in service A -> how many of type aa and in service B -> how many of type bb . As I understand every thread should open a session on it's own and then for every session I should get the correct sessionFactory and the correct results. But as it turns out this sessionFactory I guess (not sure in this statement) it belongs to every session and that is why I have mix results.
My question is if there is a way to separate somehow the sessionFactory and have clear vision of which entity how many times is saved,deleted,updated even in multithreaded environment.
If you want the statistics of a session, then call the getStatistics() method on session, which will give you the SessionStatistics. A SessionFactory should only exist once and statistics there are across all sessions.
I have now 2 tables in database:
User
user_database
In user I store login, password,role
In user_database i store database driver,url,password and user.
Diagram database
I want user login to my page and next connection what he done will be sent to user database. Why i need what? I planing map popular e commerce and create android application where user login and see store data, can add and view product orders.
Now time to go practice, my knowledge in spring technology is small please explain me something when I doing wrong.
All examples on web for AbstractRoutingDataSource have declaration datasource in persistence file or create datasource bean and start using AbstractRoutingDataSource.
In my project I don't now user connection and i need get this from database. I was try get using repository and this example
https://stackoverflow.com/a/17575648/3037869
but i getting null on #Autowired in controller, i think connection for repository is null. How to set connection for this repository and set Route? Defect this method is when i add user i need restart server to refresh connection.
Next try what i using now is class User implement UserDetails after user login i can get user connection from getPrincipal() and add to map.
private void setDataSources() {
HashMap<Object, Object> targetDataSources = new HashMap<>();
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
targetDataSources.put("auth", dataSourceBuilder.build());
setDefaultTargetDataSource(dataSourceBuilder.build());
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
User user=(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(user.getUserDatabase().getDriver());
dataSourceBuilder.driverClassName(user.getUserDatabase().getDriver());
dataSourceBuilder.url(user.getUserDatabase().getUrl());
dataSourceBuilder.username("3450_Menadzer");
dataSourceBuilder.password(user.getUserDatabase().getPassword());
targetDataSources.put("user", dataSourceBuilder.build());
}
setTargetDataSources(targetDataSources);
afterPropertiesSet(); //map is refresh when i add this
}
I run this method on constuctor and determineCurrentLookupKey
protected Object determineCurrentLookupKey() {
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
setDataSources();
return "user";
}
return "auth";
}
This is working but when i refresh 3-4 times request for user database i getting
User 3450_Menadzer already has more than 'max_user_connections' active connections
Setting connection map manualy and not refreshing every method determineCurrentLookupKey run i don't have this problem. I think my method is not clossing connection. How i can clean this? This is possible to better method to route connection?
EDIT
#SergeBallesta i change some code from your examples
This is my class for map
#Component
#Scope(value = "singleton")
public class DataSourceMap {
private Map<Object,Object> dataSourceMap;
public DataSourceMap()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
dataSourceBuilder.username("sa");
dataSourceBuilder.password("");
dataSourceMap=new HashMap<Object,Object>();
dataSourceMap.put("auth",dataSourceBuilder.build());
}
public void addDataSource(String session,DataSource dataSource)
{
this.dataSourceMap.put(session,dataSource);
}
public Map<Object,Object> getDataSourceMap()
{
return dataSourceMap;
}
public void removeSource(String session)
{
dataSourceMap.remove(session);
}
}
For AbstractRoutingDataSource i done some changes, i was add afterPropertiesSet() beacuse datasource not refresh. I done some refresh and i not getting error i think this is working. I need test this for more databases in future
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource{
#Autowired
DataSourceMap dataSources;
#Override
protected Object determineCurrentLookupKey() {
setDataSources(dataSources);
afterPropertiesSet();
System.out.println("test");
if( SecurityContextHolder.getContext().getAuthentication()!=null) {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
return request.getSession().getId();
}
return "auth";
}
#Autowired
public void setDataSources(DataSourceMap dataSources) {
System.out.println("data adding");
setTargetDataSources(dataSources.getDataSourceMap());
}
}
First, per user database is a very uncommon design. If all those databases will end with same structure, please do not do that in a real world application, but just add user_id in your tables and queries.
Next, I found another (not full) example of a dynamic AbstractRoutingDataSource in another answer of mine.
And one big difference between my code (beware never tested) and your question is that I use a SessionListener to close the databases to avoid that the number of open database grows indefinitively.
If you to this to learn Spring, you could try the following pattern (bottom-up description) :
a session scoped bean that would hold the actual database connection for a user, the connection should be created on first request (to be sure that user id is present in session) and cached for subsequent uses. A destroy method (automaticaly called by Spring when session is closed) should close the connection.
an AbstractRoutingDataSource, that would be injected with a proxy to above holder, and that would ask actual datasource to the holder
As in the other answer, if same user is likely to have many simultaneous sessions, you could have a singleton been injected in session holders that would keep the actual database connections along with the number of active sessions. That way you would get one single connection per user, no matter how many concurrent sessions he could have.
I'm trying to get my head around how Hibernate session management works. I'm trying to define a simple session-per-request model in a web application, but it just doesn't seem to be working. So far I have this:
#WebListener
public class HibernateDataAccess implements ServletRequestListener
{
Configuration configuration;
SessionFactory sessionFactory;
public HibernateDataAccess ()
{
configuration = new Configuration ().configure ();
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder ().applySettings (
configuration.getProperties ()).build ();
sessionFactory = configuration.buildSessionFactory (serviceRegistry);
}
public List<Customer> getAllCustomers () throws SQLException
{
Session session = sessionFactory.getCurrentSession ();
return (List<Customer>) session.createQuery ("select c from Customer c").list ();
}
#Override
public void requestDestroyed (ServletRequestEvent arg0)
{
ManagedSessionContext.unbind (sessionFactory).close ();
}
#Override
public void requestInitialized (ServletRequestEvent arg0)
{
System.out.println ("requestInitialized called!");
Session session = sessionFactory.openSession ();
ManagedSessionContext.bind (session);
session.beginTransaction ();
}
}
My problem is that despite the fact that I get the message "requestInitialized called!" on my server's console output, I get the following exception when I try to call getAllCustomers from within a servlet request (inside a doGet() method):
org.hibernate.HibernateException: No session currently bound to execution context
at org.hibernate.context.internal.ManagedSessionContext.currentSession(ManagedSessionContext.java:75)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1013)
at net.meridiandigital.binco.web.HibernateDataAccess.getAllCustomers(HibernateDataAccess.java:38)
at net.meridiandigital.binco.web.CustomerServlet.doGetList(CustomerServlet.java:80)
What am I doing wrong?
Have you set the hibernate.current_session_context_class configuration parameter to a Hibernate.Context.ICurrentSessionContext implementation?
The problem was quite obvious -- the instance of HibernateDataAccess I was using wasn't the same on the server was calling the listener methods on, so the session factory that was having the current session registered was a different one to the one my code was trying to use. The solutions was to separate the two concerns: have an entirely separate #WebListener class that called to my singleton HibernateDataAccess to start and close sessions.
I have a Database where user and address data is stored in separate tables. When a user logs in to my page I want to show him a form with which he can change his user/account data. However, I end up getting an Exception: org.hibernate.LazyInitializationException: could not initialize proxy - no Session. The address attributes can't be loaded. The user loads just fine and that's what I don't understand. My AddressDAOImpl looks like this:
#Autowired
private SessionFactory sessionFactory;
public void addAddress(Address address)
{
sessionFactory.getCurrentSession().save(address);
}
public void updateAddress(Address address)
{
sessionFactory.getCurrentSession().update(address);
}
public List<Address> listAddress()
{
return sessionFactory.getCurrentSession().createQuery("from address").list();
}
public void deleteAddress(int id)
{
Address addr = (Address) sessionFactory.getCurrentSession().load(Address.class, id);
if(addr != null)
{
sessionFactory.getCurrentSession().delete(addr);
}
}
public Address getAddress(int id)
{
return (Address) sessionFactory.getCurrentSession().load(Address.class, id);
}
My Controller does this:
#RequestMapping("/library/home")
public ModelAndView showHome()
{
String username = SecurityContextHolder.getContext().getAuthentication().getName();
User user = userService.getUser(username);
Address address = addressService.getAddress(user.getId());
ModelMap mmap = new ModelMap();
mmap.addAttribute("user", user);
mmap.addAttribute("addresse", address);
return new ModelAndView("/library/home", mmap);
}
Why doesn't this work? And why does it work for the user data?
I'm assuming that your AddressService created the transaction used to load the Address proxy (it's a proxy because you used load() instead of get()). The session associated with this transaction is closed when you return the Address from the AddressService.
By the time your view resolver tries to access the attributes of the Address proxy, there is no session available through which a database query can be run to fetch the attributes. Hence the exception.
Either use get() instead of load(), or access the attributes while you're still in the scope of the transaction.
The load method doesn't fetch an address from the database. It returns a proxy to an uninitialized address, assuming the address exists in the database. Use Session.get to get the address.
See http://docs.jboss.org/hibernate/core/3.6/javadocs/org/hibernate/Session.html#load%28java.lang.Class,%20java.io.Serializable%29
I think its possibly because you did not initialize your sessionfactory configuration. Try this class to help you create a session factory.
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure()
.buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
hope this helps
You can consider using an OpenSessionInView pattern even if it's not always the best solution
I'm working on a webapp and I have connection errors after Hibernate throws exceptions :
com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: No operations allowed after connection closed.
It gave me this exception each time I try to access my db after an exception occured.
I now Hibernate's not supposed to throw errors if my application is well coded but if something happens with the connection to the db, I don't want my application to be stuck with this error.
Here's my HibernateUtil class :
public class HibernateUtil {
private static Logger log = Logger.getLogger(HibernateUtil.class);
private static org.hibernate.SessionFactory sessionFactory;
private static String confFile = "hibernate-test.properties";
private static final ThreadLocal<Session> threadSession = new ThreadLocal<Session>();
private HibernateUtil() {
}
public static void buildSessionFactory(){
Configuration configuration = new Configuration();
synchronized(HibernateUtil.class){
if(sessionFactory == null){
try {
Properties properties = new Properties();
properties.load(HibernateUtil.class.getClassLoader().getResourceAsStream(confFile));
configuration.setProperties(properties);
} catch (Exception e) {
log.fatal("cannot load the specified hibernate properties file: " + confFile);
throw new RuntimeException("cannot load the specified hibernate properties file : " + confFile, e);
}
sessionFactory = configuration.configure().buildSessionFactory();
}
HibernatePBEEncryptorRegistry registry = HibernatePBEEncryptorRegistry.getInstance();
if(registry.getPBEStringEncryptor("strongHibernateStringEncryptor") == null) {
StandardPBEStringEncryptor strongEncryptor = new StandardPBEStringEncryptor();
strongEncryptor.setAlgorithm("PBEWithMD5AndDES"); // not really needed as it is the default
strongEncryptor.setPassword("aStrongPassword");
registry.registerPBEStringEncryptor("strongHibernateStringEncryptor", strongEncryptor);
}
}
}
public static SessionFactory getSessionFactory() {
if(sessionFactory == null){
buildSessionFactory();
}
return sessionFactory;
}
public static Session getCurrentSession(){
if(!getSessionFactory().getCurrentSession().isOpen())
getSessionFactory().openSession();
return getSessionFactory().getCurrentSession();
}
}
Here's my BaseAction class where initialization and closing of sessions is set :
public class BaseAction extends ActionSupport {
public Session hib_session;
public void initHibSession() {
hib_session = HibernateUtil.getCurrentSession();
hib_session.beginTransaction();
hib_session.clear();
}
public void closeHibSession() {
hib_session.getTransaction().commit();
}
}
Here's an example of an action:
Transaction transaction = new Transaction(user, Transaction.Type.REGISTRATION, new HashSet(domains));
initHibSession();
hib_session.save(transaction);
closeHibSession();
transaction_id = transaction.getId();
Is there a way to avoid the exception above ?
It gave me this exception each time I try to access my db after an exception occurred.
I'm not sure to understand the exact condition. Anyway, after an exception, you should rollback the transaction, close the session and start over. That being said, I have some remarks about your code.
About your HibernateUtil:
why do you have a ThreadLocal, the Session#getCurrentSession() method handle that for you (you don't seem to use the thread local though).
in HibernateUtil.getCurrentSession(), why do you mess with getCurrentSession() and openSession()? Firstly, there is no need to do what you do, getCurrentSession() will return a new session if no session is associated to the current thread. Secondly, both approaches are different and have different semantics (you need to close the session yourself when using openSession()), you should use one or the other.
About your BaseAction:
I wonder why you clear() the session after Session#beginTransaction(). In case you didn't committed an ongoing transaction, you'll loose all the pending changes. Is this really what you want?
PS: I would consider using the Open Session in View pattern to remove all this burden from your code.
Resources
Sessions and transactions
Open Session in View