I am using Hibernate in my web application which is developed with pure Servlet and JSP. I am facing to a big trouble "sometimes" when I execute the code. What happens is I am getting Too many Connections error from Hibernate.
I went through lot of Stackoverflow questions seeking for an answer and I found different solutions. Some suggested to use a third party pooling system, some suggested to be thread safe, some suggested to be using one SessionFactory etc, therefor I am not sure which one is applicable to mine.
Below is a portion of my database layer.
package dao;
import java.util.List;
import model.main.Familyvisa;
import model.main.Familyvisa;
import model.main.Familyvisa;
import model.main.Pensionhistory;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
/**
*
* #author user
*/
public class FamilyVisaImpl implements FamilyVisaInterface
{
private Session currentSession;
private Transaction currentTransaction;
public Session openCurrentSession() {
currentSession = getSessionFactory().openSession();
return currentSession;
}
public Session openCurrentSessionwithTransaction() {
currentSession = getSessionFactory().openSession();
currentTransaction = currentSession.beginTransaction();
return currentSession;
}
public void closeCurrentSession() {
currentSession.close();
}
public void closeCurrentSessionwithTransaction() {
currentTransaction.commit();
currentSession.close();
}
private static SessionFactory getSessionFactory() {
Configuration configuration = new Configuration().configure();
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties());
SessionFactory sessionFactory = configuration.buildSessionFactory(builder.build());
return sessionFactory;
}
public Session getCurrentSession() {
return currentSession;
}
public void setCurrentSession(Session currentSession) {
this.currentSession = currentSession;
}
public Transaction getCurrentTransaction() {
return currentTransaction;
}
public void setCurrentTransaction(Transaction currentTransaction) {
this.currentTransaction = currentTransaction;
}
#Override
public void save(Familyvisa entity) {
getCurrentSession().save(entity);
}
#Override
public void update(Familyvisa entity) {
getCurrentSession().update(entity);
}
#Override
public Familyvisa findById(int id) {
Familyvisa book = (Familyvisa) getCurrentSession().get(Familyvisa.class, id);
return book;
}
#Override
public void delete(Familyvisa entity) {
getCurrentSession().delete(entity);
}
#Override
public List<Familyvisa> findAll() {
List<Familyvisa> remDur = (List<Familyvisa>) getCurrentSession().createQuery("from Familyvisa").list();
return remDur;
}
public Familyvisa findByForiegnKey_Family(int idFamily)
{
String hql = "FROM Familyvisa WHERE idFamily = :famId";
//String hql = "FROM Visa WHERE idFamily = :famId";
Query q = getCurrentSession().createQuery(hql);
q.setParameter("famId", idFamily);
Familyvisa v = new Familyvisa();
if(!q.list().isEmpty())
{
v = (Familyvisa)q.list().get(0);
}
return v;
}
#Override
public void saveOrUpdate(Familyvisa p)
{
getCurrentSession().saveOrUpdate(p);
}
#Override
public List<Object[]> findReminderActiveVisaWithFamilyAndEmployee()
{
String sql = "";
SQLQuery createSQLQuery = getCurrentSession().createSQLQuery(sql);
return createSQLQuery.list();
}
#Override
public void batchUpdate(List<Familyvisa> list)
{
for(int i=0;i<list.size();i++)
{
getCurrentSession().update(list.get(i));
}
}
}
Below is my service layer, related to the above code.
package service;
import dao.FamilyVisaImpl;
import java.util.List;
import model.main.Familyvisa;
/**
*
* #author user
*/
public class FamilyVisaService
{
private FamilyVisaImpl familyVisaImpl;
public FamilyVisaService()
{
familyVisaImpl = new FamilyVisaImpl();
}
public Familyvisa findByForiegnKey_Family(int idFamily)
{
familyVisaImpl.openCurrentSession();
Familyvisa findByForiegnKey_Family = familyVisaImpl.findByForiegnKey_Family(idFamily);
familyVisaImpl.closeCurrentSession();
return findByForiegnKey_Family;
}
public List<Object[]> findReminderActiveVisaWithFamilyAndEmployee()
{
familyVisaImpl.openCurrentSession();
List<Object[]> visa = familyVisaImpl.findReminderActiveVisaWithFamilyAndEmployee();
familyVisaImpl.closeCurrentSession();
return visa;
}
public void batchUpdate(List<Familyvisa> list)
{
familyVisaImpl.openCurrentSessionwithTransaction();
familyVisaImpl.batchUpdate(list);
familyVisaImpl.closeCurrentSessionwithTransaction();
}
}
Below is a code from Servlet, which explains how I execute the code.
private void updateDatabase(List<VisaWithFamilyAndEmployeeBean> reminderSentList)
{
FamilyVisaService service = new FamilyVisaService();
List<Familyvisa> visa = new ArrayList<Familyvisa>();
for(int i=0;i<reminderSentList.size();i++)
{
Familyvisa familyVisa = service.findByForiegnKey_Family(reminderSentList.get(i).getIdFamily());
familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
visa.add(familyVisa);
}
service.batchUpdate(visa);
}
I have lot of classes in three layers (servlet, DAO, Service) and all follow the exact same structure, serving different purposes, but methods looks almost same (like update, insert etc).
Please pay some decent attention to the code, to the key words, usage of access specifiers etc. In some other classes, in service layer, I define it's IMPL as static as well eg: private static EmployeeImpl employeeimpl;
Can you find what is happening wrong here? Since it occures only "sometimes" and in any of the code (not only in here, but the other classes also same, only difference is they call to different tables) so I can figure it out.
UPDATE
Considering the comments and answers, I changed the code to below. Please let me know whether it is in quality level.
FamilyVisaService service = new FamilyVisaService();
Session session = service.openCurrentSession(); //This method will call openCurrentSession() in Impl class
try {
for(int i=0;i<reminderSentList.size();i++)
{
/* findByForiegnKey_Family() has Session argument now! */
Familyvisa familyVisa = service.findByForiegnKey_Family(session, reminderSentList.get(i).getIdFamily());
familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
visa.add(familyVisa);
}
} catch (Exception ex) {
System.out.println("ERROR:"+ex);
} finally {
session.close();
}
Your snippet of code:
for(int i=0;i<reminderSentList.size();i++)
{
Familyvisa familyVisa = service.findByForiegnKey_Family(reminderSentList.get(i).getIdFamily());
familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
visa.add(familyVisa);
}
opens and closes the session inside the loop multiple times during it's execution using service.findByForeignKey_Family() function.
Session openning and closing could take some time but the loop is fast enough. That's why could multiple sessions to be open: it's just need the time to be closed. And in your code it's actual. That's why "Too Many Connections" error occurs.
In other words, pass the session to the service.findByForiegnKey_Family() as parameter instead of openning and closing this inside function.
Like this:
Session session = ...
try {
for(int i=0;i<reminderSentList.size();i++)
{
/* findByForiegnKey_Family() has Session argument now! */
Familyvisa familyVisa = service.findByForiegnKey_Family(session, reminderSentList.get(i).getIdFamily());
familyVisa.setNumberOfReminders(familyVisa.getNumberOfReminders()+1);
familyVisa.setLastReminderSent(Common.getCurrentDateSQL());
visa.add(familyVisa);
}
} catch (Exception ex) {
System.out.println("ERROR:"+ex);
} finally {
session.close();
}
The above example is Thread-safe. Because you open, operate and close the session inside single function.
Hibernate requires transaction block even for read operations. So you have to fix your code like this:
Session session = ...
try {
session.beginTransaction();
...
Your Loop
...
session.getTransaction.commit();
...
Your code is wrong in so many ways:
The code is not Thread-safe, as you already admitted:
private Session currentSession;
private Transaction currentTransaction;
public Session openCurrentSession() {
currentSession = getSessionFactory().openSession();
return currentSession;
}
public Session openCurrentSessionwithTransaction() {
currentSession = getSessionFactory().openSession();
currentTransaction = currentSession.beginTransaction();
return currentSession;
}
public void closeCurrentSession() {
currentSession.close();
}
public void closeCurrentSessionwithTransaction() {
currentTransaction.commit();
currentSession.close();
}
The Service layer singletons should never store state, because they are accessed by concurrent requests. What if you have a current running Session and a second request opens a new session as well? The first thread will never get the chance to close his session, but it will attempt to close the last opened session (e.g. currentSession).
The Session is not even thread-safe so you will bump into all sorts of weird concurrent modification or change visibility errors.
You should follow the Hibernate session management best practices and choose for a session-per-request solution backed by a ThreadLocal Session storage.
Adding Spring Transaction Management is a simple and effective way of handling the connection/session/transaction management.
Related
I would like to make use of prepared statements when executing CQL in my application. This functionality looks to be provided by the ReactiveCqlTemplate class, which I have passed into the ReactiveCassandraTemplate in my Cassandra configuration here:
#Configuration
#EnableReactiveCassandraRepositories(
basePackages = "com.my.app",
includeFilters = {
#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {ScyllaPersonRepository.class})
})
public class CassandraConfiguration extends AbstractReactiveCassandraConfiguration {
#Value("${cassandra.host}")
private String cassandraHost;
#Value("${cassandra.connections}")
private Integer cassandraConnections;
#Override
public CassandraClusterFactoryBean cluster() {
PoolingOptions poolingOptions = new PoolingOptions()
.setCoreConnectionsPerHost(HostDistance.LOCAL, cassandraConnections)
.setMaxConnectionsPerHost(HostDistance.LOCAL, cassandraConnections*2);
CassandraClusterFactoryBean bean = super.cluster();
bean.setJmxReportingEnabled(false);
bean.setPoolingOptions(poolingOptions);
bean.setLoadBalancingPolicy(new TokenAwarePolicy(new RoundRobinPolicy()));
return bean;
}
#Override
public ReactiveCassandraTemplate reactiveCassandraTemplate() {
return new ReactiveCassandraTemplate(reactiveCqlTemplate(), cassandraConverter());
}
#Bean
public CassandraEntityInformation getCassandraEntityInformation(CassandraOperations cassandraTemplate) {
CassandraPersistentEntity<Person> entity =
(CassandraPersistentEntity<Person>)
cassandraTemplate
.getConverter()
.getMappingContext()
.getRequiredPersistentEntity(Person.class);
return new MappingCassandraEntityInformation<>(entity, cassandraTemplate.getConverter());
}
#Override
public SchemaAction getSchemaAction() {
return SchemaAction.CREATE_IF_NOT_EXISTS;
}
public String getContactPoints() {
return cassandraHost;
}
public String getKeyspaceName() {
return "mykeyspace";
}
}
This is the ScyllaPersonRepository referenced in my Cassandra configuration filters.
public interface ScyllaPersonRepository extends ReactiveCassandraRepository<Person, PersonKey> {
#Query("select id, name from persons where id = ?0")
Flux<Object> findPersonById(#Param("id") String id);
}
After executing a few queries, the CQL Non-Prepared statements metric in my Scylla Monitoring Dashboard showed that I'm not using prepared statements at all.
I was able to use prepared statements after followed the documentation here which walked me through creating the CQL myself.
public class ScyllaPersonRepository extends SimpleReactiveCassandraRepository<Person, PersonKey> {
private final Session session;
private final CassandraEntityInformation<Person, PersonKey> entityInformation;
private final ReactiveCassandraTemplate cassandraTemplate;
private final PreparedStatementCache cache = PreparedStatementCache.create();
public ScyllaPersonRepository(
Session session,
CassandraEntityInformation<Person, PersonKey> entityInformation,
ReactiveCassandraTemplate cassandraTemplate
) {
super(entityInformation, cassandraTemplate);
this.session = session;
this.entityInformation = entityInformation;
this.cassandraTemplate = cassandraTemplate;
}
public Flux<ScyllaUser> findSegmentsById(String id) {
return cassandraTemplate
.getReactiveCqlOperations()
.query(
findPersonByIdQuery(id),
(row, rowNum) -> convert(row)
);
}
private BoundStatement findPersonByIdQuery(String id) {
return CachedPreparedStatementCreator.of(
cache,
QueryBuilder.select()
.column("id")
.column("name")
.from("persons")
.where(QueryBuilder.eq("id", QueryBuilder.bindMarker("id"))))
.createPreparedStatement(session)
.bind()
.setString("id", id);
}
private Person convert(Row row) {
return new Person(
row.getString("id"),
row.getString("name"));
}
}
But, I would really like the ORM to handle that all for me. Is it possible to configure this behaviour out of the box, so that I don't need to manually write the CQL myself but instead just enable it as an option in my Cassandra Configuration and get the ORM to orchestrate it all behind the scenes?
Frankly, I think this is a bug(request for enhancement) and it should be filed in Springs Jira.
It seems the repository simply doesn't support this out of box(nor did I find any config option how to flip it, but I might have missed it).
Actually, my theory was correct:
https://jira.spring.io/projects/DATACASS/issues/DATACASS-578?filter=allopenissues
so just add yourself and try to ask them for resolution.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {HibernateConfigTest.class})
#Transactional
#Sql(scripts = {"api_routes.sql",
"profile.sql",
"status.sql",
"user.sql",
"game_token.sql",
"game.sql",
"message.sql"},
config = #SqlConfig(transactionMode = ISOLATED),
executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
#Sql(scripts = "delete_data.sql",
executionPhase = ExecutionPhase.AFTER_TEST_METHOD)
public class GameDaoTest {
#Autowired
private GameDao gameDao;
#Test
public void getGetRecentGames() {
Game game = null;
for (int i = 0; i < 1000; i++) {
game = new Game(i + 1000);
game.setStartedAt(DateUtils.getCurrentUTCDate());
gameDao.save("game", game);
}
List<Game> recentGames = gameDao.getRecentGames(1000);
assertNotNull(recentGames);
assertEquals(1000, recentGames.size());
}
}
When I get to the line List<Game> recentGames = gameDao.getRecentGames(1000); hibernate prints out all of the insert statements. Unfortunately, when the games are retrieved, none of the ones I inserted are retrieved. Is there any way to retrieve those games? Maybe a better question would be how do I put those inserts in their own transaction so they are persisted for the subsequent methods?
Here is the AbstractDao that GameDao extends:
public class AbstractDao {
#PersistenceContext
protected EntityManager entityManager;
protected Session getSession() {
return entityManager.unwrap(Session.class);
}
public void save(Object entity) {
getSession().save(entity);
}
public void save(String entityName, Object entity) {
getSession().save(entityName, entity);
}
public void persist(Object entity) {
getSession().persist(entity);
}
}
The persist method throws an exception for some detached entity reason that I am not familiar with.
Try calling flush on your session before calling getRecentGames. If that method is executing a custom query, as opposed to using one of Hibernate's session methods, it's going directly against the database -- but since your save hasn't yet been synced with the underlying database, the data isn't there. You don't actually need to do a true commit as long as Hibernate can see the data.
for (int i = 0; i < 1000; i++) {
game = new Game(i + 1000);
game.setStartedAt(DateUtils.getCurrentUTCDate());
gameDao.save("game", game);
}
gameDao.flush(); // calls getSession().flush()
List<Game> recentGames = gameDao.getRecentGames(1000);
// call assert methods as needed
I've a application with a complex transaction scenario.
I've a task that do many transactions. I try to simplify it to show you the concept:
I've a main task that open a transaction for every appointment readed from the db:
#Component
public class MainTask {
#Inject
TransactionTemplate transactionTemplate;
#Scheduled(cron = "*/30 * * * * ?")
public void execute() {
List<Long> appointments = new ArrayList<Long>();
transactionTemplate.execute(status -> {
try {
appointments= communicationClass.loadAppointments();
} catch (Exception e) {
log.error("", e);
status.setRollbackOnly();
}
return null;
});
appointmens.parallelStream().forEach(id -> {
transactionTemplate.execute(status -> {
try {
communicationClass.evaluateSms(id);
} catch (Exception e) {
log.error("", e);
status.setRollbackOnly();
}
return null;
});
This is the CommunicationClass:
#Component
public class CommunicationClass{
#PersistenceContext
EntityManager entityManager;
public List<Long> loadAppointments() {
//CALL A SERVICE TO GET THE LIST OF APPOINTMENTS
return appointmentService.loadAppointments();
}
public void evaluateSms(long idAppuntamento) {
boolean orarioSms = templateServiceImpl.canInviaSms(template, appuntamento);
//REMOTE CALL
boolean esitoInvio = inviaSmsGateway(appuntamento, sms, template);
if (esitoInvio == false)
throw new RuntimeException("Rollback of the entire transaction");
}
This is the TemplateServiceImpl class used before:
#Service
#Transactional
public class TemplateServiceImpl {
public boolean canInviaSms(Template template, IHasComunicazioni oggetto) {
//DO SOMETHING WITH OBJECTS RECEIVED
}
My questions are:
The transaction opened from the transactionTemplate is preserved also when there is a call to communicationClass and later to the service (annotated with #Transactional)?
The RuntimeException thrown in evaluateSms() should rollback the entire transaction opened inside the for(), right?
if is thrown an exception inside the method canInviaSms() of TemplateServiceImpl is rolledback the entire transaction?
Thanks
I'm usnig Google App Engine with Java, and using Datastore via JPA.
I want to save profile image for each user:
#Entity
public class User implements Serializable {
#Id
private String userId;
private String imageKey; // saves BlobKey.getKeyString()
// ... other fields and getter/setter methods ...
}
If user wants to change their profile image, I (should) update imageKey field AND delete old image associated with old imageKey.
But, the document of Blobstore for Python says:
Deleting a Blobstore value occurs separately from datastore transactions. If you call this method during a datastore transaction, it takes effect immediately, regardless of whether the transaction commits successfully or is retried.
This seems that I can't make updating imageKey and deleting old image as one atomic action, and it also would affect to Java.
This is my attempt to do this work:
public class Engine implements ServletContextListener {
private EntityManagerFactory emf;
private BlobstoreService blobstoreService;
// Servlet will call getUser, modify returned object, and call updateUser with that object
public User getUser(final String userId) throws EngineException {
return doTransaction(new EngineFunc<User>() { // Utility method for create Entity manager and start transaction
#Override
public User run(EntityManager em) throws EngineException {
return em.find(User.class, key);
}
});
}
public void updateUser(final User user) throws EngineException {
doTransaction(new EngineFunc<Void>() {
#Override
public Void run(EntityManager em) throws EngineException {
em.merge(user);
return null;
}
});
user.purgeOldImages(blobstoreService);
}
// ... Other methods ...
}
public class User {
#Transient
private transient Set<String> oldImageList = new CopyOnWriteArraySet<>();
public void setImageKey(String imageKey) {
if (this.imageKey != null) {
oldImageList.add(this.imageKey);
}
this.imageKey = imageKey;
if (imageKey != null) {
oldImageList.remove(imageKey);
}
}
public void purgeOldImages(BlobstoreService blobService) {
Set<BlobKey> toDelete = new HashSet<>();
for (String s : oldImageList) {
toDelete.add(new BlobKey(s));
oldImageList.remove(s);
}
blobService.delete(toDelete.toArray(new BlobKey[0]));
}
// ... Other methods ...
}
I think this is neither "beautiful" nor correct code.
Is there the right way to do this?
I have followed a tutorial on dynamic datasource routing tutorial in Spring. For that I have to extend AbstractRoutingDataSource to tell spring which datasource to get, so I do:
public class CustomRouter extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
Everything goes fine till I find the class responsible for keeping the value of the customerType (it should be the same during the whole session):
public class CustomerContextHolder {
private static final ThreadLocal<Integer> contextHolder = new ThreadLocal<Integer>();
public static void setCustomerType(Integer customerType) {
contextHolder.set(customerType);
}
public static Integer getCustomerType() {
return (Integer) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
This creates a thread-bound variable customerType, but I have a web application with spring and JSF I don't think with threads but with sessions. So I set it in the login page with thread A (View), but then thread B (Hibernate) request the value to know what datasource to use, it is null indeed, because it has a new value for this thread.
Is there any way to do it Session-bounded instead of Thread-bounded?
Things I have tried so far:
Inject the CustomRouter in the view to set it in the session: Not working, it causes a cycle in dependecies
Replace the ThreadLocal with an Integer: Not working, the value is always set by the last user logged in
Is FacesContext.getCurrentInstance() working? If so then you may try with this:
public class CustomerContextHolder {
private static HttpSession getCurrentSession(){
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance()
.getExternalContext().getRequest();
return request.getSession();
}
public static void setCustomerType(Integer customerType) {
CustomerContextHolder.getCurrentSession().setAttribute("userType", customerType);
}
public static Integer getCustomerType() {
return (Integer) CustomerContextHolder.getCurrentSession().getAttribute("userType");
}
public static void clearCustomerType() {
contextHolder.remove(); // You may want to remove the attribute in session, dunno
}
}