I've setup a basic spring project with a single database connection.
In the application.properties file I have the database settings:
spring.datasource.url = jdbc:mysql://192.168.1.19/ticket
spring.datasource.username = dbusername
spring.datasource.password = dbpassword
I've created a base DAO class which other DAOs extend:
#Transactional
public class Dao<E> {
#PersistenceContext
private EntityManager entityManager;
private Class<E> entityClass;
public Dao(Class<E> entityClass) {
this.entityClass = entityClass;
}
public void create(E object) {
entityManager.persist(object);
return;
}
public void delete(E object) {
if (entityManager.contains(object)) {
entityManager.remove(object);
} else {
entityManager.remove(entityManager.merge(object));
}
return;
}
#SuppressWarnings("unchecked")
public List<E> getAll() {
return entityManager.createQuery("from " + entityClass.getName()).getResultList();
}
public E get(long id) {
return entityManager.find(entityClass, id);
}
public void update(E object) {
entityManager.merge(object);
return;
}
}
Here's a sample entity that extends the base DAO:
#Repository
public class PersonDao extends Dao<Person> {
public PersonDao() {
super(Person.class);
}
}
Currently this uses a single database, but I need to be able to add a second database, and somehow define in each DAO which datasource to use. Each DAO will only use a single database, so there's no requirement for a DAO to be able to connect to multiple databases.
I've done some research, and that seems to suggest I need to use JdbcTemplate? but I can't seem to find a tutorial that matches my need. Also, at the minute the entityManager is injected into the DAO, but the JdbcTemplate examples I've looked at don't seem to use the entityManager, which is slightly confusing.
database.password1=<password1>
database.url1=jdbc\:mysql\://localhost\:3306/twodbone
database.username1=<username1>
database.password2=<password1>
database.url2=jdbc\:mysql\://localhost\:3306/twodbtwo
database.username2=<username2>
database.driverClassName=com.mysql.jdbc.Driver
In this way you can add the multiple databases and configure both hibernate.cfg.xml file and applicationContext.xml file also..
#Repository
public class FooRepository
{
#PersistenceContext
private EntityManager entityManager;
#Autowired(required = true)
private JdbcTemplate jdbcTemplate;
public void saveFoo(Foo foo)
{
this.entityManager.persist(foo);
}
public List<SomeReportPojo> getSomeReport()
{
return this.entityManager.queryForList("SELECT .. ",SomeProjectPojo.class);
}
}
this.jdbcTemplate should be kept rather than this.entityManager for jdbc templetes
this is simple example
Related
I am using Hibernate #Filter with Spring Data to add specific "where" clause for every query in my project. The problem is that it works as long as I use #Transactional annotation for my 'findAll' method. Is there any way to avoid using #Transactional? Why is it important here?
Here is the filter config:
#FilterDef(name = "testFilter",
parameters = #ParamDef(name = "company_id", type = "long"),
defaultCondition = "company_id=:companyId")
#Filter(name = "testFilter")
#EntityListeners(EmployeeListener.class)
#NoArgsConstructor
public class Employee {//basic fields}
Repository:
#Repository
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, UUID> {
}
Aspect that enables the filter:
#Aspect
#Component
public class TestAspect {
private final SecurityAspect securityAspect;
private final Logger logger;
#PersistenceContext
private EntityManager entityManager;
#Autowired
public TestAspect(SecurityAspect securityAspect, Logger logger) {
this.securityAspect = securityAspect;
this.logger = logger;
}
#Around("execution(* org.springframework.data.repository.CrudRepository+.findAll(..))")
public Object aroundFindAllTenantAware(ProceedingJoinPoint joinPoint) {
Session session = this.entityManager.unwrap(Session.class);
session.enableFilter("testFilter")
.setParameter("companyId", this.securityAspect.getCompanyId());
Object retValue = null;
try {
retValue = joinPoint.proceed();
} catch (Throwable throwable) {
logger.error(throwable.getMessage(), throwable);
} finally {
session.disableFilter("testFilter");
}
return retValue;
}
}
And, finally, my service:
#Service
#Transactional
public class EmployeeApiServiceImpl {
private final EmployeeRepository repository;
#Autowired
public EmployeeApiServiceImpl(EmployeeRepository repository) {
this.employeeRepository = employeeRepository; }
#Override
public Response listProfiles(SecurityContext securityContext) {
List<Employee> employees = repository.findAll();
return Response.ok().entity(employees).build();
}
Without #Transactional annotation, it does not work. When I am debugging the aspect I can see that the filter was enabled but the query did not change. When I put the annotation, everything works fine. But I don't get why it is happening. This annotation is not supposed to be on read methods, plus in every tutorial everything works without it, but not in my case.
After some research I found a solution - using TransactionTemplate class. So I just need to put my code from aspect into method 'execute' and it will do the same as #Transactional annotation. But in this case, I don't have to annotate each method in each service as Transactional. These articles helped me a lot:
https://www.baeldung.com/spring-programmatic-transaction-management
https://www.baeldung.com/transaction-configuration-with-jpa-and-spring
transactionTemplate.execute(status -> {
Session session = null;
Object result = null;
try {
session = entityManager.unwrap(Session.class);
session.enableFilter(Constants.FILTER_NAME)
.setParameter(Constants.PARAMETER_NAME, this.securityAspect.getCompanyId());
result = joinPoint.proceed();
} catch (Throwable throwable) {
logger.error(throwable.getMessage(), throwable);
} finally {
if (session != null) {
session.disableFilter(Constants.FILTER_NAME);
}
}
return result;
});
The problem is the EntityManager proxy that is injected by Spring which only supports certain operations without a transaction. You'll need a custom proxy implementation if you want support for this. See org.springframework.orm.jpa.SharedEntityManagerCreator.SharedEntityManagerInvocationHandler#invoke
Knowing that there are some frameworks to do it in real-world situation. As I learning from scratch, I wonder is that possible to create pure MongoDB DAO as a layer on top of the Service class to do CRUD operation that also facilitate other DAO use it?
For example, below is my Generic DAO class to operate CRUD process.
public interface IGenericDAO<T> {
public T create(T t);
public T update(T t);
public T get(Object id);
public void delete(Object id);
public List<T> listAll();
public Long count();
}
Then, DAO class should implements its operations
public class UserDAO implements IGenericDAO<User> {
MongoDatabase database = dBUtils.getMongoDB();
MongoCollection <Document> userTbl = database.getCollection("User");
public UserDAO() {
super();
}
#Override
public User create(User user) {
return user;
}
// other CURD below
}
User Service class
public class UserService {
private UserDAO userDAO;
public UserService() {
}
public void listUser() {
// need to test
List<User> listUsers = userDAO.listAll();
}
public void create(User user) {
// this what I want to see. user is saved to db here
try {
MongoDatabase database = dBUtils.getMongoDB();
assert mdb != null;
// create or get collection
MongoCollection<User> userTbl = database.getCollection("Users", User.class);
User userDoc = new User();
userTbl.insertOne(userDoc);
} catch (Exception e) {
e.printStackTrace();
}
}
Having said that, I want to put MongoDB "layer" between the DAO and Service class to do the CRUD operation. I wonder is it necessary, otherwise, how to do it to help the UserService with UserDAO class?
I have a need to persist(insert) a entity to database immediately when the save (or saveAndFlush) code is called.
However although the entity is created in the context it is not persisted in the database immediately.
We are using Spring Boot.
public interface MessageRepository extends JpaRepository<MessageEntity, Long> {
}
In the Service class
#Service
public class TestService {
#Autowired
private MessageRepository messageRepository;
#Transactional
public MessageEntity saveMessage(MessageEntity entity) throws Exception {
entity = messageRepository.saveAndFlush(entity);
return entity;
}
}
Though the entity is created it is not persisted/committed to the database immediately.
We are facing this issue within the Activiti Tasks only.
Any feedback will be appreciated.
This worked.
#Component
public class MessageRepositoryCustomImpl implements MessageRepositoryCustom {
#PersistenceContext
EntityManager entityManager;
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public MessageEntity saveImmediate(MessageEntity entity) {
entityManager.persist(entity);
return entity;
}
}
One way of overcoming this situation is by taking advantage of the REQUIRES_NEW transaction attribute.
In your situation you would have to create a new repository:
public interface MessageRepositoryCustom{
void saveAndFLush(MessageEntity ent);
}
public MessageRepositoryCustomImpl implements MessageRepositoryCustom{
#Autowired
private SessionFactory sessionFactory;
#Transactional(propagation = Propagation.REQUIRES_NEW)
void saveAndFLush(MessageEntity ent){
Session session = sessionFactory.getCurrentSession();
session.persist(ent);
}
}
Then in your service you would use that repository:
#Transactional
public MessageEntity saveMessage(MessageEntity entity) throws Exception {
entity = messageRepositoryCutom.saveAndFlush(entity);
// other processing
return entity;
}
}
Now after the messageRepositoryCutom.saveAndFlush method has finished processing the entity will be physically persisted in the database as it was created in a separate transaction which has been commited.
We have set up the Spring Framework like this:
#Eager
public interface CatalogElementRepository extends PagingAndSortingRepository<CatalogElementEntity, Long> {
}
#Service
public class CatalogImpl implements CatalogManager {
#Inject
CatalogElementRepository catalogElementRepository;
#Override
public CatalogElement createCatalogElement(CatalogElementEntity catalogElement) {
return this.catalogElementRepository.save(catalogElement);
}
}
#Stateless
#Remote(CatalogManager.class)
public class CatalogManagerBean implements CatalogManager {
#Inject
CatalogManager delegate;
#Override
public CatalogElement createCatalogElement(CatalogElementEntity catalogElement) {
return this.delegate.createCatalogElement(catalogElement);
}
}
So whenever someone calls the method on the remote interface createCatalogElement, I'd assume the entity gets stored in the database. It does not (weirdly enough, findOne still returns the very same entity, but it can't be found via findByProperty).
Other questions said to add #Transactional, so I added #javax.transaction.Transactional and org.springframework.transaction.annotation.Transactional on the methods and classes to be on the safe side, nothing worked.
What could be the problem?
I don't see any configuration files for the Spring Framework, but it's a legacy project, so they might just be hidden very well.
For some reason using this class as a producer for the EntityManager helped:
public class SpringConfig {
#PersistenceUnit
EntityManagerFactory emf;
#PersistenceContext
EntityManager em;
#Produces
#ApplicationScoped
public EntityManagerFactory createEntityManagerFactory() {
return this.emf;
}
#Produces
public EntityManager createEntityManager() {
return this.em;
}
public void close(#Disposes EntityManagerFactory entityManagerFactory) {
entityManagerFactory.close();
}
public void close(#Disposes EntityManager entityManager) {
entityManager.close();
}
}
I'm using hibernate 4.1.2
And I want insert some data into the table.
I know that it can be realized with sql in hibernate configuration in this way:
<property name="hibernate.hbm2ddl.import_files" value="/file1.sql,/file2.sql"/>.
But, are there any other ways insert automatically data only once in Java code after hibernate started?
I want do it like this:
public Role getRoleByName(EnumRole name)
{
return (Role) sessionFactory.getCurrentSession()
.createQuery("from Role where name = :name")
.setParameter("name", name).uniqueResult();
}
public void insertRoles(){
for(EnumRole role:EnumRole.values())
{
Role r=getRoleByName(role);
if(r==null)
{
r=new Role();
r.setName(role);
r.setDescription(role.getDescription());
sessionFactory.getCurrentSession().save(r);
}
}
EnumRole:
public enum EnumRole {
ROLE_CLIENT("РОЛЬ КЛИЕНТА"),
ROLE_ADMIN("РОЛЬ АДМИНСТРАТОРА"),
ROLE_CONSUMER("РОЛЬ КОМПАНЬОНА"),
ROLE_ANONYMOUS("НЕ АВТОРИЗОВАННЫЙ ПОЛЗОВАТЕЛЬ");
EnumRole(String descriptin)
{
this.descriptin=descriptin;
}
public String getDescription()
{
return this.descriptin;
}
private String descriptin;
}
You need to create any Spring bean with #PostConstruct annotated method and for example create transaction using PlatformTransactionManager:
#Service
public class AnyBean {
#Autowired
private PlatformTransactionManager transactionManager;
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
private void init() {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(new TransactionCallback<Object>() {
#Override
public Object doInTransaction(TransactionStatus transactionStatus) {
// HERE YOU CODE
for(EnumRole role:EnumRole.values())
{
Role r = getRoleByName(role);
if(r==null)
{
r=new Role();
r.setName(role);
r.setDescription(role.getDescription());
sessionFactory.getCurrentSession().save(r);
}
}
return null;
}
});
}
}
I hope this helped.
Create a service with method insertRoles() marked with #Transactional and call service.insertRoles().
Or, more easy:
create a transaction
add roles
commit (or manage rollback if error occured)
Do this stuff after your application startup has been completed