I'm developing an application in Spring 3.0.5 and Hibernate 3.6.2, and currently i'm working in a JSON controller, but i have this exception and i can't understand why is happening. I've checked previously in SO and Google, but this problem is quite strange. So here is my code:
Controller
#RequestMapping(value = "/props", method = RequestMethod.GET)
public #ResponseBody
List<Property> getJsonProps(String id) {
if(id==null)return null;
Device dev = deviceService.getDispositivo(Long.parseLong(id));
List<Property> props = deviceService.listProperties(dev, 10);
return props;
}
Device Service
#Service("manageDevices")
#Transactional(readOnly=true,propagation=Propagation.REQUIRED)
public class ManageDevicesImpl implements ManageDevices {
private Logger log = LoggerFactory.getLogger(getClass());
#Autowired
private DevicesDAO devicesDAO;
public List<Property> listProperties(Device dev, Integer qty) {
List<Property> props = devicesDAO.pickProperties(dev, qty);
return props;
}
}
DAO
#Repository("devicesDAO")
public class DevicesDAOImpl implements DevicesDAO {
private Logger log = LoggerFactory.getLogger(getClass());
#Autowired
private SessionFactory sessionFactory;
public List<Property> pickProperties(Device dev, Integer qty) {
if(qty >= 0){
log.debug("Open? "+ sessionFactory.getCurrentSession().isOpen());
log.debug("Tx Active? " + sessionFactory.getCurrentSession().getTransaction().isActive());
List<Property> props = dev.getProperties();
if(props != null){
if(props.size() >= qty)
return props.subList(0, qty-1);
else
return props;
}
}
return null;
}
}
The exception occurs in the pickProperties function (DAO Layer), at the line where i try to load the properties (getProperties). In the logs, there is an open session and transaction. Thanks in advance.
Could you post the exact exception you got?
You're in a transaction at the dev.getProperties() line, but not the transaction where dev was loaded. You either need to reattach it, or arrange for dev.getProperties() to be called while you're still in the transaction that loaded it, or move the transactional boundary up so that both calls are in the same transaction, or change the Hibernate configuration so that properties isn't lazy loaded, or change the code that loads dev so that it fetches properties in HQL.
Which of those options will apply to you depends on your situation, but I'd start with the last one.
Looks like your transaction is created after the Device dev is read. Try reading/rereading it within the transaction to see what happens.
Related
I'm a little bit new to hibernate, so I started with simple things.
According to F.I.R.S.T test principles, unit tests must be I - isolated.
I'm trying to apply it to integration tests for repository layer (Hibernate\JPA) using #Transactional annotation:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = RepositoryConfig.class)
#Transactional
public class EmployeeRepositoryTest extends AbstractRepositoryTest {
#Autowired
private IEmployeeRepository employeeRepository;
#Test
public void saveTest() {
Employee expectedEmployee = buildEmployee(1, "Parker");
employeeRepository.save(expectedEmployee);
Employee actualEmployee = employeeRepository.findById(1);
assertEquals(expectedEmployee, actualEmployee);
}
private Employee buildEmployee(long id, String name) {
Employee employee = new Employee();
employee.setId(id);
employee.setName(name);
return employee;
}
}
However, as far as two methods are performed within a transaction, hibernate does not actually perform them (as I understand it) - at least there's no line with insert in logs.
If I run data insertion by adding a script to embeded datasourse like:
INSERT INTO employee (employee_id, employee_name) VALUES (1, 'name');
and try to save employee with the same id but new name, the test will success. And that's the most confusing thing for me.
I saw a solution with autowiring EntityManager and calling it's flush() method. But I don't like it, since I try to write tests without being tied to Hibernate\JPA.
I also tried different flushMode, but it didn't help either.
Q1: Is there a way to make Hibernate run queries right after repository's method is called?
Q2: Is it a good practice to call EntityManager#flush in save/update/delete repository methods explicitly?
My Employee:
#Entity
#Table(name = "employee")
public class Employee {
#Id
#Column(name = "employee_id")
private long id;
#Column(name = "employee_name")
private String name;
// the rest required things (constructor, getters/setters and etc)
}
and RepositoryConfig:
#Configuration
#EnableTransactionManagement
#ComponentScan("org.my.package")
public class RepositoryConfig {
#Bean
public DataSource getDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
#Bean
public JpaTransactionManager transactionManager() {
return new JpaTransactionManager();
}
#Bean
#Autowired
public HibernateTemplate getHibernateTemplate(SessionFactory sessionFactory) {
return new HibernateTemplate(sessionFactory);
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setPackagesToScan("org.my.package.model");
sessionFactory.setHibernateProperties(getHibernateProperties());
return sessionFactory;
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "H2Dialect");
properties.put("hibernate.show_sql", true);
return properties;
}
}
You have no option but to interact with the entity manager to get these tests working as you expect - not to trigger a flush (as that can be done by calling saveAndFlush(..) method on your repository rather than just save(...)) but to clear the first level cache:
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html#saveAndFlush-S-
#Test
public void saveTest() {
Employee expectedEmployee = buildEmployee(1, "Parker");
//call save and flush for immediate flush.
employeeRepository.saveAndFlush(expectedEmployee);
//now you will need to clear the persistence context to actually
//trigger a load from the database as employee with id 1 is already
//in the persistence context.
//without the below you will not see a db select
entityManager.clear();
Employee actualEmployee = employeeRepository.findById(1);
assertEquals(expectedEmployee, actualEmployee);
}
An alternative to clearing the persistence context is to fall back to using raw JDBC to read the updated row(s).
But I don't like it, since I try to write tests without being tied to Hibernate\JPA. You are testing a persistence mechanism implemented in Hibernate\JPA and your repository is just an abstraction that is allowing you to avoid direct calls to it so this seems a slightly ridiculous statement.
I'm simply unable to inject EntityManagerFactory object from ProfileDBUtil into ProfileManager class where I actually apply the transactions to look for an entry in the database, or update a row and so.
createEntityManager() method throws the exception specified in the end of the entry.
The weird thing is that the same piece of code works for UAT environment while it fails for SIT environment. All config is the same for both of the environments. Class loader order, shared lib references, module class loaders are the same.
Both of the environments have the necessary jars in their file system and seem to be loaded successfully in runtime.
What would be the root cause for this problem?
public class ProfileDBUtil {
private static final String PERSISTENCE_UNIT = "com.profile.userdb";
public boolean loadProfile(String memberID) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT);
ProfileManager upm = new ProfileManager(emf);
List < Profile > ProfileList = upm.searchProfile(memberID);
}
}
#SuppressWarnings("unchecked")
#JPAManager(targetEntity = com.profile.userdb.model.Profile.class)
public class ProfileManager {
private EntityManagerFactory emf;
public ProfileManager() {
}
public ProfileManager(EntityManagerFactory emf) {
this.emf = emf;
}
public void setEntityManagerFactory(EntityManagerFactory emf) {
this.emf = emf;
}
private EntityManager getEntityManager() {
if (emf == null) {
throw new RuntimeException("The EntityManagerFactory is null. This must be passed in to the constructor``");
}
return emf.createEntityManager(); // THIS FAILS
}
}
Log trace;
at org.hibernate.engine.transaction.internal.jta.JtaStatusHelper.getStatus(JtaStatusHelper.java:73)
at org.hibernate.engine.transaction.internal.jta.JtaStatusHelper.isActive(JtaStatusHelper.java:115)
at org.hibernate.engine.transaction.internal.jta.CMTTransaction.join(CMTTransaction.java:149)
at org.hibernate.ejb.AbstractEntityManagerImpl.joinTransaction(AbstractEntityManagerImpl.java:1207)
at org.hibernate.ejb.AbstractEntityManagerImpl.postInit(AbstractEntityManagerImpl.java:176)
at org.hibernate.ejb.EntityManagerImpl.<init>(EntityManagerImpl.java:89)
at org.hibernate.ejb.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:125)
Edit of Neil; "JtaStatusHelper is a HIBERNATE class!" rang the bell for me. I went and checked the props of emf as the following;
Map<String, Object> prop = emf.getProperties();
logger.debug("emf props : " + prop.toString());
And observed the differences between SIT and UAT environments.
The basic difference was that openjpa was not available in any keys of the prop for SIT while it was all over the places for UAT. The config for SIT was somehow turned into hibernate related lines. However, I was out after having things in place according to JPA settings.
It simply proved that something is wrong with the last installation of the application into Server. Thus, I uninstalled the app and install it from scratch.
It made things right.
I have a web service DocGenerationServiceImpl that inserts (for every format) a record in the table using DocRepository and object representing the record as DocFileDO. In the for-loop, I can get the id of the record that was created in the table. For each record, I will call the executor's execute method where DocGenTask will search for the record given the id. However, for example, there are 3 formats, the DocGenTask is able to get only the last record. The first 2 it cannot find. Although it's using hibernateTemplate. Can please advise?
#RestfulService
#Controller
#RequestMapping("/docs")
public class DocGenerationServiceImpl {
#Autowired
private TaskExecutor taskExecutor;
#Autowired
private DocRepository docRepository;
#RequestMapping(value = "/generate", method = RequestMethod.POST)
#ResponseBody
public String generatedDocFile(DOCParam param) {
for(String format : param.getFormatList()) {
DocFileDO docFileDO = new DocFileDO();
...
docRepository.saveDocFile(docFileDO);
log.debug("docFileDO id = " + docFileDO.getId());
DocGenTask task = new DocGenTask(docFileDO.getId());
task.setDocRepository(docRepository);
taskExecutor.execute(task);
}
}
}
#Repository
public class DocRepository {
#Autowired
private HibernateTemplate hibernateTemplate;
public DocFileDO saveDocFile(DocFileDO docFile) {
hibernateTemplate.save(docFile);
hibernateTemplate.flush();
return docFile;
}
public DocFileDO getDocFile(Long docFileId) {
return hibernateTemplate.get(DocFileDO.class, docFileId);
}
}
public class DocGenTask implements Runnable {
public void run() {
generate();
}
private void generate() {
DocFileDO docFileObj = docRepository.getDocFile(docFileId);
}
}
A couple of things
Don't use HibernateTemplate it should be considered deprecated as of Hibernate 3.0.1 (which was released somewhere in 2006). Use the SessionFactory directly and use the getCurrentSession() method to get a hibernate Session to operate on.
You don't have transactions setup (judging from the snippets), to work with a databse you need proper transaction setup.
Your controller is doing much, all of this should be inside a service.
The first refactor your repository
#Repository
public class DocRepository {
#Autowired
private SessionFactory sf;
public DocFileDO saveDocFile(DocFileDO docFile) {
Session session = sf.getCurrentSession();
session.save(docFile);
return docFile;
}
public DocFileDO getDocFile(Long docFileId) {
return sf.getCurrentSession().get(DocFileDO.class, docFileId);
}
}
Now your code will probably fail due to improper transaction setup. Add #Transactional to all the methods (or class) that need a transaction (like the saveDocFile method).
As mentioned you probably should move the code found in the controller to a service. The controller should be nothing more then a thin integration layer converting from the web to an internal representation of something and then kick off a service/business method somewhere. This service-/business-method is also your transactional unit-of-work it either all succeeds or all fails.
I'm currently having the issue that the #Transactional annotation doesn't seem to start a transaction for Neo4j, yet (it doesn't work with any of my #Transactional annotated methods, not just with the following example).
Example:
I have this method (UserService.createUser), which creates a user node in the Neo4j graph first and then creates the user (with additional information) in a MongoDB. (MongoDB doesn't support transactions, thus create the user-node first, then insert the entity into MongoDB and commit the Neo4j-transaction afterwards).
The method is annotated with #Transactional yet a org.neo4j.graphdb.NotInTransactionException is thrown when it comes to creating the user in Neo4j.
Here is about my configuration and coding, respectively:
Code based SDN-Neo4j configuration:
#Configuration
#EnableTransactionManagement // mode = proxy
#EnableNeo4jRepositories(basePackages = "graph.repository")
public class Neo4jConfig extends Neo4jConfiguration {
private static final String DB_PATH = "path_to.db";
private static final String CONFIG_PATH = "path_to.properties";
#Bean(destroyMethod = "shutdown")
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(DB_PATH)
.loadPropertiesFromFile(CONFIG_PATH).newGraphDatabase();
}
}
Service for creating the user in Neo4j and the MongoDB:
#Service
public class UserService {
#Inject
private UserMdbRepository mdbUserRepository; // MongoRepository
#Inject
private Neo4jTemplate neo4jTemplate;
#Transactional
public User createUser(User user) {
// Create the graph-node first, because if this fails the user
// shall not be created in the MongoDB
this.neo4jTemplate.save(user); // NotInTransactionException is thrown here
// Then create the MongoDB-user. This can't be rolled back, but
// if this fails, the Neo4j-modification shall be rolled back too
return this.mdbUserRepository.save(user);
}
...
}
Side-notes:
I'm using spring version 3.2.3.RELEASE and spring-data-neo4j version 2.3.0.M1
UserService and Neo4jConfig are in separate Maven artifacts
Starting the server and SDN reading operations work so far, I'm just having troubles with writing operations
I'm currently migrating our project from the tinkerpop-framework to SDN-Neo4j. This user creation-process has worked before (with tinkerpop), I just have to make it work again with SDN-Neo4j.
I'm running the application in Jetty
Does anyone have any clue why this is not working (yet)?
I hope, this information is sufficient. If anything is missing, please let me know and I'll add it.
Edit:
I forgot to mention that manual transaction-handling works, but of course I'd like to implement it the way "as it's meant to be".
public User createUser(User user) throws ServiceException {
Transaction tx = this.graphDatabaseService.beginTx();
try {
this.neo4jTemplate.save(user);
User persistantUser = this.mdbUserRepository.save(user);
tx.success();
return persistantUser;
} catch (Exception e) {
tx.failure();
throw new ServiceException(e);
} finally {
tx.finish();
}
}
Thanks to m-deinum I finally found the issue. The problem was that I scanned for those components / services in a different spring-configuration-file, than where I configured SDN-Neo4j. I moved the component-scan for those packages which might require transactions to my Neo4jConfig and now it works
#Configuration
#EnableTransactionManagement // mode = proxy
#EnableNeo4jRepositories(basePackages = "graph.repository")
#ComponentScan({
"graph.component",
"graph.service",
"core.service"
})
public class Neo4jConfig extends Neo4jConfiguration {
private static final String DB_PATH = "path_to.db";
private static final String CONFIG_PATH = "path_to.properties";
#Bean(destroyMethod = "shutdown")
public GraphDatabaseService graphDatabaseService() {
return new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(DB_PATH)
.loadPropertiesFromFile(CONFIG_PATH).newGraphDatabase();
}
}
I still will have to separate those components / services which require transactions from those which don't, though. However, this works for now.
I assume that the issue was that the other spring-configuration-file (which included the component-scan) was loaded before Neo4jConfig, since neo4j:repositories has to be put before context:component-scan. (See Note in Example 20.26. Composing repositories http://static.springsource.org/spring-data/data-neo4j/docs/current/reference/html/programming-model.html#d0e2948)
I am performing integration tests by using embedded Glassfish 3.1.2. The first thing I do in the test is to reset the database so each test have a completely fresh database to play with.
However, the problem is that the objects are persisted in the shared cache and not stored in the database. So when the next test starts it will get the old records from the cache instead of the database.
I can easily get rid of the problem by define
<property name="eclipselink.cache.shared.default" value="false"/>
in my persistence.xml file.
#BeforeClass
public static void startup() throws Exception {
container = EJBContainer.createEJBContainer();
context = container.getContext();
}
#Before
public void setUp() throws Exception {
//Clean database before every test using dbunit
}
#Test // This is the first test, works well since the test is first in order
public final void testCreateUser() throws Exception {
UserService userService = (UserService) context.lookup("java:global/galleria/galleria-ejb/UserService");
User user = new User(TEST_USER_ID, TEST_PASSWORD);
User actualUser = userService.signupUser(user);
assertTrue(actualUser != null);
assertEquals(TEST_USER_ID, actualUser.getUserId());
assertFalse(Arrays.equals(TEST_PASSWORD, actualUser.getPassword()));
logger.info("Finished executing test method {}", testMethod.getMethodName());
}
#Test // This is the second test, fails since the database not is clean
public final void testCreateUser() throws Exception {
UserService userService = (UserService) context.lookup("java:global/galleria/galleria-ejb/UserService");
User user = new User(TEST_USER_ID, TEST_PASSWORD);
User actualUser = userService.signupUser(user); // FAILS since TEST_USER_ID already in cache!!
//..
}
#Stateless
#EJB(name = "java:global/galleria/galleria-ejb/UserService", beanInterface = UserService.class)
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public class UserServiceImpl implements UserService
{
#EJB
private UserRepository userRepository;
#Override
#PermitAll
public User signupUser(User user) throws UserException {
User existingUser = userRepository.findById(user.getUserId());
if (existingUser != null)
{
logger.error("Attempted to create a duplicate user.");
throw new UserException(DUPLICATE_USER);
}
try {
user = userRepository.create(user);
} catch (EntityExistsException entityExistsEx) {
logger.error("Attempted to create a duplicate user.");
throw new UserException(DUPLICATE_USER, entityExistsEx);
}
return user;
}
//..
}
However, I do not want to disable caching in persistence.xml file, since I will get performance loss later on. I only want to do it while testing. Note that I am using JTA data source here.
Any ideas?
Off topic, I am trying to learn java ee, and following the Galleria EE project and try to modify it for my needs.
Best regards
Check out http://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching
as both JPA 2.0 and EclipseLink native api allow clearing the shared cache. You could call this api at the start or end of your tests.