Lock a entity at database level using Hibernate and HSQLDB - java

I have a system where I need to create a new instance of SomeEntity in a concurrent context, using another entity as "monitor" as exemplified below:
//Begin transaction
Monitor monitor = (Monitor) session.load(Monitor.class, monitor.getId(), LockOptions.UPGRADE);
SomeEntity entity = createSomeEntity(monitor);
//Save entity and commit transaction
When I'm using Mysql this works perfectly with lock happening when I'm loading monitor with LockOptions.UPGRADE, but when I'm using HSQLDB the code above don't work and all threads run without lock at the database level, causing creation of many instances of SomeEntity in database.
Main Question
I need to lock a entity at database level using Hibernate and HSQLDB.
Examples
To exemplify what I need, I have created a simple project where I have a entity class called Person with two attributes (id an name) and a main program that will update the name attribute in a concurrent way for a single instance (Person#id = 1). There are 3 threads competing to update and all will be synchronized at the database level through the following instruction:
Person person = (Person) session.load(Person.class, 1L, LockOptions.UPGRADE);
Using the code above only one thread at once will update the name attribute and all other threads will wait to obtain the lock.
The lock, if happens, can be visualized in the sql code generated by Hibernate and will looks like this:
select
person0_.id as id1_0_0_,
person0_.name as name2_0_0_
from
Person person0_
where
person0_.id=? for update
or can be visualized during debug on Eclipse IDE, with a breakpoint at line where session.load(...) is called (only one thread go to the next instruction, all other threads wait).
So, remembering again: this works on Mysql but not in HSQLDB.
Attachments
package model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
public Person() {}
public Person(String name) {
this();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package app;
import static java.util.concurrent.Executors.newFixedThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import model.Person;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
public class Main {
private static final SessionFactory sessionFactory = init();
public static void main(String[] args) {
populate();
changeNames("Christine", "Isabelle", "Katarina");
}
private static void changeNames(String... names) {
ExecutorService executor = newFixedThreadPool(3);
for (final String name : names) {
executor.execute(new Runnable() {
private Session session = sessionFactory.openSession();
public void run() {
Transaction transaction = null;
try {
transaction = session.beginTransaction();
// At next line I put a breakpoint to debug in Eclipse Juno SR2
Person person = (Person) session.load(Person.class, 1L, LockOptions.UPGRADE);
person.setName(name);
session.saveOrUpdate(person);
transaction.commit();
System.out.println("Name changed to " + name);
} catch (Exception e) {
if (transaction != null) {
transaction.rollback();
}
e.printStackTrace();
}
finally {
session.close();
}
}
});
}
try {
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void populate() {
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
session.save(new Person("Old Name"));
transaction.commit();
session.close();
}
private static SessionFactory init() {
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
return configuration.buildSessionFactory(serviceRegistry);
}
}
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory name="">
<mapping class="model.Person" />
</session-factory>
</hibernate-configuration>
hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.dialect = org.hibernate.dialect.HSQLDialect
hibernate.connection.url = jdbc:hsqldb:mem:test;hsqldb.tx=locks;hsqldb.write_delay=false
hibernate.connection.username = sa
hibernate.connection.password =
hibernate.hbm2ddl.auto = update
#hibernate.connection.driver_class = com.mysql.jdbc.Driver
#hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
#hibernate.connection.url = jdbc:mysql://localhost/test
#hibernate.connection.username = root
#hibernate.connection.password =
hibernate.show_sql = true
hibernate.format_sql = true
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.12.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.2.9</version>
</dependency>
</dependencies>
</project>

It seems there is a hibernate HSQLDialect bug. It doesn't put a for update in the generated SQL: https://hibernate.atlassian.net/browse/HHH-7479
The bug seem to be resolved in latest beta: 4.3.0.Beta1

Related

Recently updated db records not reflected in follow-on SELECT

I have a spring mvc app with an h2 database. Db objects are updated using the JPA criteria and spring #Transaction annotation. See findBySymbol() and update() at bottom.
After update() is done, the app calls findBySymbol() to run some logic on the updated records. The findBySymbol() SQL is correct. The problem is that results from findBySymbol() have not been updated. This is puzzling because Hibernate trace logs show the update binded parameters, the SQL update statement, and the int result of executeUpdate(). Also confusing is that I can query the db at that point directly and see that the records are, in fact, updated.
What do I need to do to be able to have the app query the db immediately after the update and return updated data?
import com.example.mycompany.beans.AEarnings;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import org.springframework.stereotype.Repository;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.CriteriaUpdate;
import javax.persistence.NoResultException;
import org.springframework.transaction.annotation.Transactional;
#Repository
public class AnnualEarningsDaoImpl implements AnnualEarningsDao {
#PersistenceContext
EntityManager entityManager;
#Override
public List<AEarnings> findBySymbol(String s) {
// CriteriaBuilder
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// CriteriaQuery
CriteriaQuery<AEarnings> cQuery = cb.createQuery(AEarnings.class);
// Root
Root<AEarnings> ae = cQuery.from(AEarnings.class);
// Parameters
ParameterExpression<String> p = cb.parameter(String.class);
// Db command
cQuery.select(ae).where(cb.equal(ae.get("symbol"), p));
// TypedQuery
TypedQuery<AEarnings> query = entityManager.createQuery(cQuery);
// Set Parameter
query.setParameter(p, s);
// Results
List<AEarnings> results = query.getResultList();
// Return
return results;
}
#Override
#Transactional
public void update(
String a, LocalDate b, String c,
BigDecimal d, BigDecimal e, BigDecimal f) {
// CriteriaBuilder
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
// CriteriaUpdate<AEarnings>
CriteriaUpdate<AEarnings> update
= builder.createCriteriaUpdate(AEarnings.class);
// Root
Root<AEarnings> root = update.from(AEarnings.class);
// Holds parameters
List<Predicate> predicates = new ArrayList<>();
// Predicates
predicates.add(builder.equal(root.get("a"), a));
predicates.add(builder.equal(root.get("b"), b));
predicates.add(builder.equal(root.get("c"), c));
// Update
update
.set(root.get("d"), d)
.set(root.get("e"), e)
.set(root.get("f"), f)
.where(predicates.toArray(new Predicate[]{}));
// Execute
int i = entityManager.createQuery(update).executeUpdate();
System.out.println("Updated: " + i);
}
}
Updated with AEarningsServiceImpl, which in this case only calls dao methods:
import com.example.mycompany.beans.AEarnings;
import com.example.mycompany.dao.AEarningsDao;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
#Service
public class AEarningsServiceImpl implements AEarningsService{
#Autowired
AEarningsDao aEarningsDao;
#Override
public List<AnnualEarnings> findBySymbol(String sym) {
return annualEarningsDao.findBySymbol(sym);
}
#Override
#Transactional
public void update(
String symbol, LocalDate fiscalEndDate, String fiscalPeriod,
BigDecimal prev, BigDecimal diff, BigDecimal diffPercent) {
annualEarningsDao.update(symbol, fiscalEndDate, fiscalPeriod,
prev, diff, diffPercent);
}
// save()...
// delete()...
}
Updated with pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>appmaker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Spring Boot Stock App</description>
<name>app</name>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.5</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring4</artifactId>
<version>3.0.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Updated with code that calls AEarningsService:
package com.example.mycompany.beans.helpers;
import com.example.mycompany.beans.AnnualEarnings;
import com.example.mycompany.beans.Token;
import com.example.mycompany.beans.Url;
import com.example.mycompany.service.AnnualEarningsService;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
#Controller
public class AnnualHelper {
private static final BigDecimal HUNDRED = new BigDecimal(100);
private static final String EARNINGS_4_TAG = "/earnings/4?period=annual&token=";
private static final String EARNINGS_1_TAG = "/earnings/1?period=annual&token=";
private Url url;
#Autowired
private AnnualEarningsService annualEarningsService;
// Call class methods, filter for earnings of size > 0 for initial run
public List<AnnualEarnings> run(String domain, String token, BigDecimal percent) {
// Holds results
List<AnnualEarnings> results = new ArrayList<>();
// Grab symbols
...
// Loop through incoming symbols
symbols.forEach((symbol) -> {
// Grab most recent fiscalPeriod for symbol
List<AnnualEarnings> exists = annualEarningsService.findBySymbol(symbol);
// Check if symbol is in db
// If exists, log
if (!exists.isEmpty()) {
log(...);
// Else, process new symbol
} else {
try {
List<AnnualEarnings> earnings = processEarnings(symbol, percent);
// Filter for List<AnnualEarnings> greater than zero, add to results
if (!earnings.isEmpty()) {
// Add to results
earnings.forEach(e -> results.add(e));
}
} catch (Exception ex) {
logInfo("runAEInitial() - Exception: ", ex);
}
}
});
return results;
}
// For one symbol, parse, save, update initial earnings
private List<AnnualEarnings> processEarnings(String symbol, BigDecimal percent) {
// Create Url
...
// Grab api response
String response = "";
try {
response = CommonUtils.callApi(url);
} catch (IOException ex) {
log(...);
}
// Catch empty json object returned from API
if (response.length() == 2) {
return Collections.emptyList();
}
// Parse json from api, save to db
parseAndSaveEarnings(response);
// Update AnnualEarnings Objects
updateEarnings(symbol);
// From db, grab symbol's eearnings for last 4 quarters
List<AnnualEarnings> updatedEarnings = annualEarningsService.findBySymbol(symbol);
return filterByDiffPercent(updatedEarnings, percent);
}
// Update previous, diff values of AnnualEarnings objects just created
private void updateEarnings(String symbol) {
// Grab all objects from db for symbol
List<AnnualEarnings> inList =
annualEarningsService.findBySymbol(symbol);
// Update prev & diff values of incoming AnnualEarnings object
for (int i = 1; i < inList.size(); i++) {
// Existing object fiscalEndDate
LocalDate fiscalEndDate = inList.get(i - 1).getFiscalEndDate();
// Existing object fiscalPeriod
String fiscalPeriod = inList.get(i - 1).getFiscalPeriod();
// Get original
BigDecimal current = inList.get(i - 1).getActualEPS();
// Get previous
BigDecimal prev = inList.get(i).getActualEPS();
// Get diff
BigDecimal diff = current.subtract(prev);
// Get diffPercent
BigDecimal diffPercent = (diff.divide(prev, 4, RoundingMode.HALF_EVEN)).movePointRight(2);
// Align num signs
diffPercent = CommonUtils.alignSigns(diff, diffPercent);
// Update
annualEarningsService.update(symbol, fiscalEndDate, fiscalPeriod, prev, diff, diffPercent);
}
}
// Parse json string returned from API, save to db
private void parseAndSaveEarnings(String str) {
// Create parser
JSONParser parser = new JSONParser();
JSONObject fullObj;
try {
// Full json object
fullObj = (JSONObject) parser.parse(str);
// First layer
JSONArray earningsArray = (JSONArray) fullObj.get("earnings");
// Symbol
String jsonSymbol = fullObj.get("symbol").toString();
// Loop through earnings for one symbol
earningsArray.forEach((earningsJson) -> {
logInfo("", "");
// Create jsonobject
JSONObject jsonObject = (JSONObject) earningsJson;
// Grab values
String jsonFiscEndDateStr = jsonObject.get("fiscalEndDate").toString();
LocalDate jsonFiscEndDate = CommonUtils.convertStringToLocalDateAnnualEarnings(jsonFiscEndDateStr);
String jsonFiscPer = jsonObject.get("fiscalPeriod").toString();
// Create update object
AnnualEarnings e = new AnnualEarnings();
// Grab data, set properties
String actEps = jsonObject.get("actualEPS").toString();
e.setFiscalEndDate(jsonFiscEndDate);
e.setSymbol(jsonSymbol);
e.setActualEPS(new BigDecimal(actEps));
e.setFiscalPeriod(jsonFiscPer);
// Save
annualEarningsService.save(e);
});
} catch (ParseException ex) {
logSevere("parseAndSaveAEarningsInitial()", ex.getMessage());
}
}
// Filter objects with earnings difference > 25%
private List<AnnualEarnings> filterByDiffPercent(List<AnnualEarnings> updatedEarnings, BigDecimal percent) {
// Hold results
List<AnnualEarnings> results = new ArrayList<>();
// Loop through objects, filter out objects with diffPercent > 25
updatedEarnings.forEach((earn) -> {
// THIS IS WHERE I FIND THAT ENTITY OBJECTS RETURNED HAVE NOT BEEN
// UPDATED
logInfo("filterByDiffPercent() - earn: ", earn.toString());
BigDecimal diffPercent = earn.getDiffPercent();
if (diffPercent != null && diffPercent.compareTo(percent) == 1) {
results.add(earn);
}
});
return results;
}
}
To be able to use annotation #Transactional you need to configure your Spring with #EnableTransactionManagement annotation or XML analog: <tx:annotation-driven/>.
And properly init TransactionManager bean.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/EnableTransactionManagement.html
Because of Spring #Transactional annotation works through Aspects.
Update:
Instead of having #Transactional both at service and repo methods, please keep only at one place.
Remove the #Transactional from service method. Keep #Transactional at both findBySymbol and update methods of repo impl.
Initial Observation before the service class was shown:
This is because Hibernate uses cache in same session. (L1 cache by default)
In order to force a L1 cache refresh you can use the refresh(Object) method of Session.
From the Hibernate Docs,
Re-read the state of the given instance from the underlying database. It is inadvisable to use this to implement long-running sessions that span many business tasks. This method is, however, useful in certain special circumstances. For example
- where a database trigger alters the object state upon insert or update
- after executing direct SQL (eg. a mass update) in the same session
- after inserting a Blob or Clob

Hibernate SessionFactory - correct way to create factory, session and handle resources

For teaching purposes, I am creating Hibernate console applications. Since Hibernate is quite complex, I want to be sure that I handle the initialization, exceptions, and shutdown correctly. The examples that I have found are not optimal in my opinion. (Official Hibernate examples use test cases.)
I have HibernateUtil for initialization and shutdown of Hibernate.
package com.zetcode.util;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
public class HibernateUtil {
private SessionFactory sessionFactory;
public HibernateUtil() {
createSessionFactory();
}
private void createSessionFactory() {
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure() // configures settings from hibernate.cfg.xml
.build();
sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
// try {
// sessionFactory = new MetadataSources(registry).buildMetadata().buildSessionFactory();
// } catch (Exception e) {
// The registry would be destroyed by the SessionFactory, //but we had trouble building the SessionFactory
// so destroy it manually.
// StandardServiceRegistryBuilder.destroy(registry);
}
}
public SessionFactory getSessionFactory() {
// if (sessionFactory == null) {
// createSessionFactory();
// }
return sessionFactory;
}
public void shutdown() {
getSessionFactory().close();
}
}
Here is a console Java application:
package com.zetcode.main;
import com.zetcode.bean.Continent;
import com.zetcode.bean.Country;
import com.zetcode.util.HibernateUtil;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class Application {
public static void main(String[] args) {
HibernateUtil hutil = new HibernateUtil();
SessionFactory sessionFactory = hutil.getSessionFactory();
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
Continent europe = new Continent("Europe");
Continent asia = new Continent("Asia");
Country svk = new Country("Slovakia");
Country hun = new Country("Hungary");
Set<Country> europeCountries = new HashSet<>();
europeCountries.add(svk);
europeCountries.add(hun);
europe.setCountries(europeCountries);
Country chi = new Country("China");
Country afg = new Country("Afganistan");
Set<Country> asiaCountries = new HashSet<>();
asiaCountries.add(chi);
asiaCountries.add(afg);
asia.setCountries(asiaCountries);
session.save(europe);
session.save(asia);
// moved
//session.getTransaction().commit();
Query query = session.createQuery("SELECT c FROM Country c");
List<Country> countries = query.getResultList();
countries.stream().forEach((x) -> System.out.println(x));
Query query2 = session.createQuery("SELECT c FROM Continent c");
List<Continent> continents = query2.getResultList();
continents.stream().forEach((x) -> System.out.println(x));
session.getTransaction().commit();
} finally {
hutil.shutdown();
}
}
}
The example works OK. But, is this code correct? I am using Hibernate 5.2.
Edit I have incorporated the suggestions from the comments into the code.

Connecting to PostgreSQL-server with Hibernate from IntelliJ

I am attempting to persist data to a PostgreSQL-database via Hibernate, have put in my user/pass, checked that it's working, made a db and some tables.
When I compile, I get an error
org.postgresql.util.PSQLException: The server
requested password-based authentication, but no password was provided.
I'm using IntelliJ Ultimate 2017.1 and have tried using both the supplied pg driver and 42.00 as external library.
I've had it working in previous versions, but never seen this one before. Must admit I'm not very good at this.
Basically, my defined password does not get correctly passed on to the server. It seems like it recognized my username. I've temporarily evaded this problem by modifying my pg_hba.conf file to trust local connections without password, but I am going to persist the data on an online server, so I'm gonna need a better fix.
The driver has a standard URL template that looks like this:
jdbc:postgresql:{database::postgres}[\?<&,user={user:param},password={password:param},{:identifier}={:param}>]
Here is my hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:postgresql://localhost:5432/gigahertz</property>
<property name="connection.driver_class">org.postgresql.Driver</property>
<mapping class="no.hvl.dat101.gigahertz.ReservationJPA"/>
<!-- <property name="connection.username"/> -->
<!-- <property name="connection.password"/> -->
<!-- DB schema will be updated if needed -->
<!-- <property name="hbm2ddl.auto">update</property> -->
</session-factory>
Here is my generated Main-class
package no.hvl.dat101.gigahertz;
import org.hibernate.HibernateException;
import org.hibernate.Metamodel;
import org.hibernate.query.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import javax.persistence.metamodel.EntityType;
import java.util.Map;
/**
*/
public class Main {
private static final SessionFactory ourSessionFactory;
static {
try {
Configuration configuration = new Configuration();
configuration.configure();
ourSessionFactory = configuration.buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static Session getSession() throws HibernateException {
return ourSessionFactory.openSession();
}
public static void main(final String[] args) throws Exception {
final Session session = getSession();
try {
System.out.println("querying all the managed entities...");
final Metamodel metamodel = session.getSessionFactory().getMetamodel();
for (EntityType<?> entityType : metamodel.getEntities()) {
final String entityName = entityType.getName();
final Query query = session.createQuery("from " + entityName);
System.out.println("executing: " + query.getQueryString());
for (Object o : query.list()) {
System.out.println(" " + o);
}
}
} finally {
session.close();
}
}
}
Any advice appreciated!
The solution to this was to add the parameters in the try block in main in the form configuration.setProperty("hibernate.connection.username","u‌​sername) etc.
For some reason, the login details were not passed correctly by IntelliJ to the server.

How to call a #Remote EJB in another client project

I need to be able to call a save() method from this simple swing java app to my web app that is published on the server with the beans I use to save a new Entity type class Persona, that has name, address, email.
Im using wildfly 8.x for my server and have my web app published like this:
23:51:10,641 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-3) JNDI bindings for session bean named ContactoDAO in deployment unit deployment "proyectobase.war" are as follows:
java:global/proyectobase/ContactoDAO!edu.ups.appdis.proyectobase.negocio.ContactoDAO
java:app/proyectobase/ContactoDAO!edu.ups.appdis.proyectobase.negocio.ContactoDAO
java:module/ContactoDAO!edu.ups.appdis.proyectobase.negocio.ContactoDAO
java:global/proyectobase/ContactoDAO
java:app/proyectobase/ContactoDAO
java:module/ContactoDAO
This is my ContactoDAO Bean:
package edu.ups.appdis.proyectobase.negocio;
import java.io.Serializable;
import java.util.List;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import edu.ups.appdis.proyectobase.modelo.Persona;
#Stateless
// #Remote(Serializable.class)
#Remote
public class ContactoDAO implements Serializable {
// #Inject
// private Logger log;
/**
*
*/
#Inject
private EntityManager em;
public void save(Persona persona) {
// if (em.find(Persona.class, persona.getCodiog()) == null) {
insertar(persona);
// } else {
//
// update(persona);
// }
}
public void test() {
System.out.println("si funciona ");
}
public void insertar(Persona persona) {
em.persist(persona);
}
public void update(Persona persona) {
em.merge(persona);
}
public void remove(int codigo) {
Persona persona = em.find(Persona.class, codigo);
em.remove(persona);
}
public Persona read(int codigo) {
System.out.println("insertado objeto persona");
return em.find(Persona.class, codigo);
}
public List<Persona> getContactos() {
String sql = "SELECT p FROM Persona p";
Query query = em.createQuery(sql, Persona.class);
List<Persona> personas = query.getResultList();
return personas;
}
}
On my simple client swing app I have it set up like this:
package cliente.gui;
import java.awt.EventQueue;
............
import javax.ejb.EJB;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.security.Security;
import java.util.Hashtable;
import edu.ups.appdis.proyectobase.modelo.Persona;
import edu.ups.appdis.proyectobase.negocio.*;
public class guiPersona {
// #EJB(lookup = "java:global/proyectobase/ContactoDAO!edu.ups.appdis.proyectobase.negocio.ContactoDAO")
#EJB(lookup = "java:global/proyectobase/ContactoDAO")
ContactoDAO contactoDAO;
private JFrame frame;
private JTextField textNombre;
private JTextField textDireccion;
private JTextField textEmail;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
guiPersona window = new guiPersona();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*
* #throws NamingException
*/
public guiPersona() {
initialize();
}
...........
JButton btnGuardar = new JButton("Guardar");
btnGuardar.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
contactoDAO.test();
if (textDireccion.getText() != "" && textEmail.getText() != "" && textNombre.getText() != "") {
Persona p = new Persona();
p.setNombre(textNombre.getText());
p.setDireccion(textDireccion.getText());
p.setEmail(textEmail.getText());
contactoDAO.save(p);
textNombre.setText("");
textDireccion.setText("");
textEmail.setText("");
}
}
});
You can see in the code above I use this to call my bean in the other proyect:
#EJB(lookup = "java:global/proyectobase/ContactoDAO")
ContactoDAO contactoDAO;
And on my button to save the new entry I use this:
if (textDireccion.getText() != "" && textEmail.getText() != "" && textNombre.getText() != "") {
Persona p = new Persona();
p.setNombre(textNombre.getText());
p.setDireccion(textDireccion.getText());
p.setEmail(textEmail.getText());
contactoDAO.save(p);
textNombre.setText("");
textDireccion.setText("");
textEmail.setText("");
}
}
});
I also tried using this:
#EJB(lookup="java:global/proyectobase/ContactoDAO!edu.ups.appdis.proyectobase.negocio.ContactoDAO")
ContactoDAO contactoDAO;
I keep getting a null pointer exception on my ContactoDAO but is it maybe because the lookup is not finding anything or Im not using it right, I dont really know. My question is what would be another way of calling my save method from my bean in another simple swing project, or maybe Im missing something else whenever I use the #EJB?
EDIT:
This is my beans.xml in case you were wondering.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd" bean-discovery-mode="all">
</beans>
From the log it seems that your ContactDAO and all its dependencies are correct getting initialized on the server. On the standalone swing client possibly you are missing jboss-ejb-client.properties in your META-INF folder or you can also explicitly set the initial context properties and do the JNDI lookup manually.
SE POST
Also you should make sure to include the jboss-client.jar file in the classpath of swing client project.
Wildfly Developer Guide
If you get a Authentication failed exception then you need to add username/password properties to the InitialContext and run the add-user.sh script on the server
Add User

Spring's SimpleNamingContextBuilder and LDAP

I'm currently trying to develop a new module for our existing web application. I'm trying to add LDAP functionality and have problems initializing the LDAP context as the SimpleNamingContextBuilder registers a context that is not working together with the LdapTemplate.
In our spring applicationContext.xml we have several JNDI lookups, so before running a test case I have to create mock JNDI-Resources using the SimpleNamingContextBuilder in the test cases constructor.
SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
builder.bind("someJNDIname",someObject); //e.g. for some datasource
builder.activate();
In our Spring application-context-test.xml we have the following ldapConfiguration:
<bean id="ldapContextSource" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" value="ldap://ourserver:389" />
<property name="base" value="CN=Groups,CN=ourcompany,DC=com" />
<property name="userDn" value="CN=binduser" />
<property name="password" value="password" />
</bean>
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
<constructor-arg ref="ldapContextSource" />
</bean>
We run the testcase with:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:application-context-test.xml"})
public class TestClass {
public TestClass(){
.. //init the SimpleNamingContextBuilder
}
#Autowired
private LdapTemplate template;
#Test
public void someTestcase(){
ldapTemplate.search("", "(objectclass=user)" ,new LdapUserMapper());
}
}
As the SimpleNamingContextBuilder is already registering a simple InitialContext I get the following error:
org.springframework.ldap.NotContextException: DirContext object is required.; nested exception is javax.naming.NotContextException: DirContext object is required.
at org.springframework.ldap.support.LdapUtils.convertLdapException(LdapUtils.java:198)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:319)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:259)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:571)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:556)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:411)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:431)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:451)
at com.somecompany.TestClass.someTestcase(TestClass.java:30)
[...]
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: javax.naming.NotContextException: DirContext object is required.
at javax.naming.directory.InitialDirContext.castToDirContext(InitialDirContext.java:106)
at javax.naming.directory.InitialDirContext.getURLOrDefaultInitDirCtx(InitialDirContext.java:112)
at javax.naming.directory.InitialDirContext.search(InitialDirContext.java:245)
at org.springframework.ldap.core.LdapTemplate$4.executeSearch(LdapTemplate.java:253)
at org.springframework.ldap.core.LdapTemplate.search(LdapTemplate.java:293)
... 35 more
The error tells me that the LDAP requires a DirContext. How can I get the SimpleNamingContextBuilder to create and use such a DirContext.
If I don't register the SimpleNamingContextBuilder then creating the LDAPTemplate will work. However I will run into other problems as other parts of the application require the JNDI lookups.
I did not manage to get the SimpleNamingContextBuilder to create an instance of DirContext, but using a custom DirContextBuilder was the solution to get around this limitation.
The mocked SimpleNamingContext is mainly there to provide bound objects via e.g.
InitialContext.doLookup(String name)
methods - to let those ones work and provide proper support for e.g. LDAP DirContext instances, just omit the check for the "activated" context - you will bootstrap your code to apply activate() anyway, so this is no problem - at least not for the given JNDI + LDAP test case.
Missing this check, the code looks for the "java.naming.factory.initial" environment key and if the environment is empty (this is the case for InitialContext.doLookup(String name)) you get the mocked SimpleNamingContext with your bound objects.
If you use the LdapTemplate the environment is not empty and the key "java.naming.factory.initial" is set to "com.sun.jndi.ldap.LdapCtxFactory" or something similar which is at least (hopefully) a DirContext.
In this case you get a working DirContext instance back from the createInitialContextFactory call and the LdapTemplate lookup is successful.
So just create a class DirContextBuilder - take the code from SimpleNamingContextBuilder - like this:
public class DirContextBuilder implements InitialContextFactoryBuilder {
...
public InitialContextFactory createInitialContextFactory(Hashtable<?,?> environment) {
if (environment != null) {
...
}
Omit the check for activated == null and you are ready to test your bound JNDI objects and have a working LdapTemplate.
I faced the same issue. But overcome it with the below trick
#BeforeClass
public static void setUp(){
OracleDataSource ods = null;
try {
ods= new OracleDataSource();
} catch (SQLException e) {
e.printStackTrace();
}
ods.setURL("jdbc:oracle:thin:#(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=....;
ods.setUser(..);
ods.setPassword(..);
SimpleNamingContextBuilder builder = null;
try {
builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder();
builder.bind("some/name", ods);
} catch (NamingException e) {
e.printStackTrace();
}
}
#Before
public void beforeTest(){
SimpleNamingContextBuilder.getCurrentContextBuilder().deactivate();
}
#Test
public void yourTest(){
.....
}
This will bind your database with some/name and also let you to connect to the ldap correctly.
I also faced the same issue. I researched causes and internal behaviour in Java and SpringLdap why it happens. I came to the following decision.
I customized ContextSource bean creation in order to solve it. This method is crutch and requires modification config that checking test mode. But it works.
Below I present simple project demostrated it. For embeded LDAP server I used Apache Directory Server.
CommonConfig.java consisted this crutch:
package com.stackoverflow.question8325740.config;
import com.stackoverflow.question8325740.JndiExplorer;
import com.stackoverflow.question8325740.LdapSettings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextSource;
import org.springframework.ldap.core.support.LdapContextSource;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.NoInitialContextException;
import javax.naming.directory.DirContext;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.spi.InitialContextFactory;
import javax.naming.spi.NamingManager;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
#Configuration
public class CommonConfig {
private static class CustomLdapContextSource extends AbstractContextSource {
#Override
protected DirContext getDirContextInstance(Hashtable<String, Object> environment) throws NamingException {
return new CustomLdapContext(environment, null);
}
}
private static class CustomLdapContext extends InitialLdapContext {
public CustomLdapContext() throws NamingException {
}
public CustomLdapContext(Hashtable<?, ?> environment, Control[] connCtls) throws NamingException {
super(environment, connCtls);
}
#Override
protected Context getDefaultInitCtx() throws NamingException {
String className = "com.sun.jndi.ldap.LdapCtxFactory";
InitialContextFactory factory;
try {
factory = (InitialContextFactory) Class.forName(className).newInstance();
} catch (Exception e) {
NoInitialContextException ne =
new NoInitialContextException(
"Cannot instantiate class: " + className);
ne.setRootCause(e);
throw ne;
}
return factory.getInitialContext(myProps);
}
}
private static boolean checkTestMode() {
//checking test mode using reflection in order to not collapse in real execution
try {
Class clazz = Class.forName("org.springframework.mock.jndi.SimpleNamingContextBuilder");
Object result = clazz.getMethod("getCurrentContextBuilder").invoke(null);
return NamingManager.hasInitialContextFactoryBuilder() && result != null;
} catch (Exception e) {
return false;
}
}
#Bean
#Autowired
public ContextSource ldapContextSource(LdapSettings ldapSettings) {
AbstractContextSource contextSource;
if (checkTestMode()) {
contextSource = new CustomLdapContextSource();
} else {
contextSource = new LdapContextSource();
}
contextSource.setUrl(ldapSettings.getUrl());
contextSource.setUserDn(ldapSettings.getLogin());
contextSource.setPassword(ldapSettings.getPassword());
contextSource.setPooled(true);
contextSource.setAnonymousReadOnly(false);
Map<String, Object> baseEnvironmentProperties = new HashMap<String, Object>();
baseEnvironmentProperties.put(Context.SECURITY_AUTHENTICATION, "simple");
baseEnvironmentProperties.put(Context.REFERRAL, "follow");
contextSource.setBaseEnvironmentProperties(baseEnvironmentProperties);
return contextSource;
}
#Bean
#Autowired
public LdapOperations ldapTemplate(ContextSource ldapContextSource) {
return new LdapTemplate(ldapContextSource);
}
#Bean
public JndiExplorer jndiExplorer() {
return new JndiExplorer();
}
}
MainTest.java using JNDI and LdapOperations:
package com.stackoverflow.question8325740;
import com.stackoverflow.question8325740.config.CommonConfig;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapOperations;
import org.springframework.ldap.query.LdapQueryBuilder;
import org.springframework.ldap.test.LdapTestUtils;
import org.springframework.mock.jndi.SimpleNamingContextBuilder;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import java.util.List;
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {ApacheDsEmbededConfiguration.class, CommonConfig.class})
public class MainTest {
public static final String TEST_VALUE = "testValue";
private static SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
#BeforeAll
public static void setUp() throws Exception {
builder.bind(JndiExplorer.JNDI_TEST, TEST_VALUE);
builder.activate();
LdapTestUtils.startEmbeddedServer(ApacheDsEmbededConfiguration.PORT, ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX, "test");
Thread.sleep(1000);
}
#AfterAll
public static void shutdown() throws Exception {
LdapTestUtils.shutdownEmbeddedServer();
builder.deactivate();
}
#Autowired
private JndiExplorer jndiExplorer;
#Autowired
private LdapOperations ldapOperations;
#Test
public void testLdapTemplateWithSimpleJndi() {
Assertions.assertEquals(TEST_VALUE, jndiExplorer.getValue());
String cn = "testCN";
String sn = "testSN";
Attributes attrs = new BasicAttributes();
attrs.put("objectClass", "inetOrgPerson");
attrs.put("cn", cn);
attrs.put("sn", sn);
ldapOperations.bind("cn=" + cn + "," + ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX, null, attrs);
AttributesMapper<String> mapper = new AttributesMapper<String>() {
#Override
public String mapFromAttributes(Attributes attributes) throws NamingException {
return (String) attributes.get("sn").get();
}
};
List<String> sns = ldapOperations.search(LdapQueryBuilder.query().base(ApacheDsEmbededConfiguration.DEFAULT_PARTITION_SUFFIX).attributes("*").where("sn").is(sn), mapper);
Assertions.assertEquals(1, sns.size());
String resultSn = sns.get(0);
Assertions.assertEquals(sn, resultSn);
}
}
ApacheDsEmbededConfiguration.java:
package com.stackoverflow.question8325740;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class ApacheDsEmbededConfiguration {
//default password
static final String PASSWORD = "secret";
//default admin DN
static final String PRINCIPAL = "uid=admin,ou=system";
static final String DEFAULT_PARTITION_SUFFIX = "dc=stackoverflow,dc=com";
static final int PORT = 1888;
#Bean
public LdapSettings ldapSettings() {
LdapSettings settings = new LdapSettings();
settings.setUrl("ldap://localhost:" + PORT);
settings.setLogin(PRINCIPAL);
settings.setPassword(PASSWORD);
return settings;
}
}
Pojo LdapSettings.java:
package com.stackoverflow.question8325740;
public class LdapSettings {
private String url;
private String login;
private String password;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Bean using JNDI-variable JndiExplorer.java:
package com.stackoverflow.question8325740;
import javax.annotation.Resource;
public class JndiExplorer {
public static final String JNDI_TEST = "com/anything";
#Resource(mappedName = JNDI_TEST)
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
And pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stackoverflow</groupId>
<artifactId>question-8325740</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.3.2</junit.version>
<spring.version>5.1.4.RELEASE</spring.version>
<spring.ldap.version>2.3.2.RELEASE</spring.ldap.version>
<apacheDirectoryService.version>1.5.5</apacheDirectoryService.version>
<apacheDirectoryService.shared.version>0.9.15</apacheDirectoryService.shared.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>${spring.ldap.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-test</artifactId>
<version>${spring.ldap.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-core-entry</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-shared</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-ldap</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-server-jndi</artifactId>
<version>${apacheDirectoryService.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.directory.shared</groupId>
<artifactId>shared-ldap</artifactId>
<version>${apacheDirectoryService.shared.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
</plugins>
</build>
</project>

Categories