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.
Related
Let's say I have a Customer entity with a list of Vehicles:
#Document
public class Customer {
private List<Vehicle> vehicles;
//... getters, setters
}
Vehicle is an abstract class with a few subtypes:
public abstract class Vehicle {
}
#TypeAlias("CAR")
public class Car {
}
#TypeAlias("BOAT")
public class Boat {
}
#TypeAlias("MOTORBIKE")
public class Motorbike {
}
Is there any way to have Spring handle this use case? i.e. if I save a Car and a Boat against a customer, have them correctly hydrate when querying the Customer? At the moment, I'm getting a java.lang.InstantiationError as Spring Data seems to be trying to create an instance of the Vehicle abstract class.
Managed to resolve the issue.
Basically, I needed to add the package containing the Vehicle classes to get scanned to my Mongo Configuration class as follows:
public class CustomerDbConfig extends AbstractMongoConfiguration {
...
#Override
protected Collection<String> getMappingBasePackages() {
Collection<String> mappingBasePackages = new ArrayList<>();
mappingBasePackages.add(Vehicle.class.getPackageName());
return mappingBasePackages;
}
}
I think the above should work in most cases. My understanding is the above might not be necessary if Configuration class is within the same package as the Vehicle classes however, in my case, they're in two different packages.
Additionally, it was a bit more complicated on my side since I have multiple Mongo databases with different configurations and different MongoTemplate beans.
Initially, I was creating the MongoTemplate as follows:
#Primary
#Bean(name = "customerMongoTemplate")
public MongoTemplate customerMongoTemplate() {
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), getDatabaseName());
MappingMongoConverter converter = (MappingMongoConverter) mongoTemplate.getConverter();
converter.setCustomConversions(customConversions());
converter.afterPropertiesSet();
return mongoTemplate;
}
However, getting the MappingMongoConverter this way, via the MongoTemplate means that getMappingBasePackages was never being called. I tried to get converter instead by doing the following:
#Primary
#Bean(name = "customerMongoTemplate")
public MongoTemplate customerMongoTemplate() {
return new MongoTemplate(mongoDbFactory(), mappingMongoConverter());
}
however it didn't work, as the mongoDbFactory() and mappingMongoConverter() were returning beans for a different MongoDB configuration... This would be the ideal solution for me, but not sure how to get it working reliably with multiple configuration classes.
In the end, I managed to get working reliably as follows:
#Primary
#Bean(name = "customerMongoTemplate")
public MongoTemplate customerMongoTemplate() throws Exception {
SimpleMongoDbFactory mongoDbFactory = new SimpleMongoDbFactory(mongoClient(), getDatabaseName());
MongoMappingContext mongoMappingContext = mongoMappingContext();
mongoMappingContext.setInitialEntitySet(getInitialEntitySet());
mongoMappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), mongoMappingContext);
converter.setCustomConversions(customConversions());
converter.afterPropertiesSet();
return new MongoTemplate(mongoDbFactory, converter);
}
I'm not entirely comfortable with the above approach, it's somewhat finicky and could potentially cause issues with new versions of Spring, however, it does work.
I've setup a spring MVC application for a web application and I'm using Hibernates implementation of JPA 2.1.
I've created my models and am able to interact with the database just fine.
I've also decided to use service classes which will manage returning the entities. What I've done is created a BaseService class, so all other service classes will expand on this, and they'll have access to common functions such as create(), delete(), update() and list().
My problem is I'm unsure as to when I should be creating the EntityManager and when I should be closing it?
Currently, in my controller I'm initiating the required services when the controller loads;
#Controller
#RequestMapping("/mycontroller")
public class TestController {
CarService carService = new CarService();
ShowroomService showroomService = new ShowroomService();
}
}
Here is the BaseService that each other service extends;
public class Service<Ety> {
EntityManager em = null;
public Class<Ety> entityClass;
public Service(Class<Ety> entityClass) {
this.entityClass = entityClass;
em = JPAUtil.getEntityManager();
}
public Ety get(int id) {
Ety object = null;
em.getTransaction().begin();
object = em.find(entityClass, id);
em.getTransaction().commit();
return object;
}
public List list() {
List<Ety> objects;
em.getTransaction().begin();
objects = em.createQuery("SELECT c FROM "+entityClass.getName()+" c").getResultList();
em.getTransaction().commit();
return objects;
}
public void save(Ety object) {
em.getTransaction().begin();
em.persist(object);
em.getTransaction().commit();
}
public void update(Ety object) {
em.getTransaction().begin();
em.merge(object);
em.getTransaction().commit();
}
public void delete(Ety object) {
em.getTransaction().begin();
em.remove(object);
em.getTransaction().commit();
}
}
Here's an example Service which expands the above;
public class CarService extends Service<Car> {
public CarService() {
super(Car.class);
}
}
As you can see, I'm creating an EntityManager when the service is created, but at the moment I'm not closing it anywhere.
I'm I creating the entity manager in the correct place? when should I close it.
I had considered putting the entity manager in a static property and creating it within a filter, and then closing it at the end of the application, however I do believe this wouldn't be thread safe and would cause issues?
Any advice would be appreciated.
your CarService should be a spring bean and the instance is created from spring. NOT from your code. The same with the EntityManager. You can use the entityManager with the #autowired annotation.
You open a new EntityManager for each transaction.
This EntityManager is like a Bag mapped to the database, but with zero entity managed inside when it's just opened.
When you work with it, this Bag will be filled with some entities and Hibernate will work to create the adequate requests.
You will close this Bag to save memory at the end of the transaction.
Of course there is some tricks to have many transactions for a given EntityManager, but you have the most general idea. As always it depends...
If you use a framework like Spring or JavaEE, it will open and close the EntityManager, as well starting and committing transactions for you. You have only your business work to write.
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'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.