I read in a book that whenever we want a Java-based configuration and want define a bean we use #Bean annotation. But when I did that I got the error: The annotation #Bean is disallowed for this location. My bean is:
package com.mj.cchp.bean;
import javax.validation.constraints.Digits;
import javax.validation.constraints.NotNull;
import org.springframework.context.annotation.Bean;
import com.mj.cchp.annotation.Email;
#Bean
public class UserBean {
#NotNull
#Email
private String email;
#NotNull
private String firstName;
#NotNull
private String lastName;
#Digits(fraction = 0, integer = 10)
private String phoneNo;
#NotNull
private String role;
public String getEmail() {
return email;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getPhoneNo() {
return phoneNo;
}
public String getRole() {
return role;
}
public void setEmail(String email) {
this.email = email;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setPhoneNo(String phoneNo) {
this.phoneNo = phoneNo;
}
public void setRole(String role) {
this.role = role;
}
}
The #Bean annotation is to define a Bean to be loaded in the Spring container. it is similar to the xml config of specifying
<bean id="myId" class="..."/>
This should be used in a Configuration file(java). Which is similar to your applicationContext.xml
#Configuration
#ComponentScan("...")
public class AppConfig{
#Bean
public MyBean myBean(){
return new MyBean();
}
}
The #Bean, #Configuration and other newly introduced annotations will do exactly what you do in an Xml config.
The #Bean annotation tells Spring that a method annotated with #Bean will return an object that should be registered as a bean in the Spring application context.
So you need a UserBeanConfig class that will be annotated using #Configuration that will have a method that create the new bean.
#Configuration
public class UserBeanConfig {
#Bean
public UserBean userBean(){
return new UserBean();
}
}
From my point of view Spring is not designed to construct simple Domain object.
You should use Spring to bootstrap the dependencies of Service/DAO etc.
So I suggest avoiding spring for domain objects.
Related
I have a mongodb springboot application that is connected to 2 different databases, that have the same collection names and database names but different uris.
Here is my application.properties
spring.data.mongodb.uri = uri
spring.data.mongodb.secondDB.uri = uri
spring.data.mongodb.database = database_name
spring.data.mongodb.secondDB.database = database_name
My AppConfiguration file
#Configuration
public class MultipleMongoConfig {
#Primary
#Bean(name = "newdb1Properties")
#ConfigurationProperties(prefix = "spring.data.mongodb")
public MongoProperties getNewDb1Props() throws Exception {
return new MongoProperties();
}
#Bean(name = "newdb2Properties")
#ConfigurationProperties(prefix = "spring.data.mongodb.secondDB")
public MongoProperties getNewDb2Props() throws Exception {
return new MongoProperties();
}
#Primary
#Bean(name = "newdb1MongoTemplate")
public MongoTemplate newdb1MongoTemplate() throws Exception {
return new MongoTemplate(newdb1MongoDatabaseFactory(getNewDb1Props()));
}
#Bean(name ="newdb2MongoTemplate")
public MongoTemplate newdb2MongoTemplate() throws Exception {
return new MongoTemplate(newdb2MongoDatabaseFactory(getNewDb2Props()));
}
#Primary
#Bean
public MongoDatabaseFactory newdb1MongoDatabaseFactory(MongoProperties mongo) throws Exception {
return new SimpleMongoClientDatabaseFactory(
mongo.getUri()
);
}
#Bean
public MongoDatabaseFactory newdb2MongoDatabaseFactory(MongoProperties mongo) throws Exception {
return new SimpleMongoClientDatabaseFactory(
mongo.getUri()
);
}
Then I set up config files for each data source
#Configuration
#EnableMongoRepositories(basePackages = {"com.example.app.firstDatabse.Repository"},
mongoTemplateRef = "newdb1MongoTemplate"
)
public class NewDb1Config {
}
and
#Configuration
#EnableMongoRepositories(basePackages = {"com.example.app.secondDatabse.Repository"},
mongoTemplateRef = "newdb2MongoTemplate"
)
public class NewDb1Config {
}
For Model I have the following
#AllArgsConstructor
#NoArgsConstructor
#ToSting
#Document(collection = "coll")
public class FirstModel{
#Id
public String id;
#Field("f_name")
public String firstName;
#Field("l_name")
public String lastName;
#Field("age")
public int age;
#Field("gender")
public String gender;
}
and my second Model is the same
#AllArgsConstructor
#NoArgsConstructor
#ToSting
#Document(collection = "coll")
public class SecondModel{
#Id
public String id;
#Field("f_name")
public String firstName;
#Field("l_name")
public String lastName;
#Field("age")
public int age;
#Field("gender")
public String gender;
}
My controller
#ResController
#RequestMapping("/controller")
public class Controller{
#Autowired
private FirstDataabseRepository repo;
#Autowired
private SecondDataabseRepository repo;
#Resource
private MongoTemplate mongoTemplate;
#RequestMapping("/findByName")
public List<SecondModel> findByName(){
Criteria criteria = new Criteria();
criteria = Criteria.where("f_name").is("John");
Query q = new Query(criteria);
List<SecondModel> results = mongoTemplate.find(q,SecondModel.class);
return results;
}
}
So the results show the results of the first collection not the second one. What do I need to do for mongoTemplate to query the second collection not the first one.
Solution
I have to add a qualifier and have a mongoTemplate for each collection
#ResController
#RequestMapping("/controller")
public class Controller{
#Autowired
private FirstDataabseRepository repo;
#Autowired
private SecondDataabseRepository repo;
#Resource
#Qualifier(value="newdb1MongoTemplate")
private MongoTemplate mongoTemplate;
#Resource
#Qualifier(value="newdb2MongoTemplate")
private MongoTemplate mTemplate;
#RequestMapping("/findByName")
public List<SecondModel> findByName(){
Criteria criteria = new Criteria();
criteria = Criteria.where("f_name").is("John");
Query q = new Query(criteria);
List<SecondModel> results = mTemplate.find(q,SecondModel.class);
return results;
}
}
I am new to batch programming and I am using Spring Batch. tried to use some online examples but after fixing lot of errors i am getting an empty file. I am trying to read from a simple MySql database and writing to a CSV file. users.csv is located in resource folder. Spent a lot of time to see what wrong I am doing. There is no error message but it is always showing empty file. I would really appreciate if anyone can help me to fix this issue. Here is my code;
BatchConfig.class
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
private DataSource dataSource;
#Bean
public JdbcCursorItemReader<User> reader(){
JdbcCursorItemReader<User> cursorItemReader = new JdbcCursorItemReader<>();
cursorItemReader.setDataSource(dataSource);
cursorItemReader.setSql("SELECT user_id,first_name,last_name,email FROM demodb.user");
cursorItemReader.setRowMapper(new UserRowMapper());
return cursorItemReader;
}
#Bean
public UserItemProcessor processor(){
return new UserItemProcessor();
}
#Bean
public FlatFileItemWriter<User> writer(){
FlatFileItemWriter<User> writer = new FlatFileItemWriter<User>();
writer.setResource(new ClassPathResource("users.csv"));
DelimitedLineAggregator<User> lineAggregator = new DelimitedLineAggregator<User>();
lineAggregator.setDelimiter(",");
BeanWrapperFieldExtractor<User> fieldExtractor = new BeanWrapperFieldExtractor<User>();
fieldExtractor.setNames(new String[]{"userId","firstName","lastName", "email"});
lineAggregator.setFieldExtractor(fieldExtractor);
writer.setLineAggregator(lineAggregator);
return writer;
}
#Bean
public Step step1(){
return stepBuilderFactory
.get("step1")
.<User,User>chunk(100)
.reader(reader())
.processor(processor())
.writer(writer())
.build();
}
#Bean
public Job exportUserJob(){
return jobBuilderFactory
.get("exportUserJob")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
}
UserRowMapper.class
public class UserRowMapper implements RowMapper<User> {
#Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setUserId(rs.getInt("user_id"));
user.setFirstName(rs.getString("first_name"));
user.setLastName(rs.getString("last_name"));
user.setEmail(rs.getString("email"));
return user;
}
}
User.class
public class User {
private int userId;
private String firstName;
private String lastName;
private String email;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
UserItemProcessor.class
public class UserItemProcessor implements ItemProcessor<User, User>{
#Override
public User process(User person) throws Exception {
return person;
}
}
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/demodb
spring.datasource.password=root
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.batch.initialize-schema=ALWAYS
MySql db
UPDATE
I could solve the issue with new file new FileSystemResource(). But I was replicating a business use case issue. As I mentioned I am new to Spring batch and I have a requirement to read from a production database where I have only View permission. I come to know that Spring batch uses metadata tables to process the Batch. So is it required to have a write permission to the table ? If that is the case then I wont be able to use spring batch with only View permission in production environment. Is there any way I can work around this ? Overriding any configuration ?
Try to decrease chunk value
chunk: Indicates that this is an item based step and the number of items to be processed before the transaction is committed.
Use FileSystemResource instead of ClassPathResource in your writer's configuration:
writer.setResource(new FileSystemResource("users.csv"));
I have verified this by running this program, is working fine as it is and no issue
Issue:
As I mentioned in the comments below, user is looking at the file at the wrong location.
User creates a file at src/main/resources/users.csv
User runs the program
Files gets copied under target/classes/users.csv
Program is writing to the file under target/classes/users.csv
But user was looking the file at src/main/resources/users.csv
I created a small example project to show two problems I'm experiencing in the configuration of Spring Boot validation and its integration with Hibernate.
I already tried other replies I found about the topic but unfortunately they didn't work for me or that asked to disable Hibernate validation.
I want use a custom Validator implementing ConstraintValidator<ValidUser, User> and inject in it my UserRepository.
At the same time I want to keep the default behaviour of Hibernate that checks for validation errors during update/persist.
I write here for completeness main sections of the app.
Custom configuration
In this class I set a custom validator with a custom MessageSource, so Spring will read messages from the file resources/messages.properties
#Configuration
public class CustomConfiguration {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
}
The bean
Nothing special here if not the custom validator #ValidUser
#ValidUser
#Entity
public class User extends AbstractPersistable<Long> {
private static final long serialVersionUID = 1119004705847418599L;
#NotBlank
#Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
#Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String landlinePhone;
#Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String mobilePhone;
#NotBlank
#Column(nullable = false, unique = true)
private String username;
#Email
private String email;
#JsonIgnore
private String password;
#Min(value = 0)
private BigDecimal cashFund = BigDecimal.ZERO;
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLandlinePhone() {
return landlinePhone;
}
public void setLandlinePhone(String landlinePhone) {
this.landlinePhone = landlinePhone;
}
public String getMobilePhone() {
return mobilePhone;
}
public void setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public BigDecimal getCashFund() {
return cashFund;
}
public void setCashFund(BigDecimal cashFund) {
this.cashFund = cashFund;
}
}
Custom validator
Here is where I try to inject the repository. The repository is always null if not when I disable Hibernate validation.
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
#Autowired
private UserRepository userRepository;
#Override
public void initialize(ValidUser constraintAnnotation) {
}
#Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername(value.getUsername());
if (foundUser != null && foundUser.getId() != value.getId()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error("", e);
return false;
}
return true;
}
}
messages.properties
#CUSTOM VALIDATORS
ValidUser.message = I dati inseriti non sono validi. Verificare nuovamente e ripetere l'operazione.
ValidUser.unique.username = L'username [${validatedValue.getUsername()}] è già stato utilizzato. Sceglierne un altro e ripetere l'operazione.
#DEFAULT VALIDATORS
org.hibernate.validator.constraints.NotBlank.message = Il campo non può essere vuoto
# === USER ===
Pattern.user.landlinePhone = Il numero di telefono non è valido. Dovrebbe essere nel formato E.123 internazionale (https://en.wikipedia.org/wiki/E.123)
In my tests, you can try from the source code, I've two problems:
The injected repository inside UserValidator is null if I don't disable Hibernate validation (spring.jpa.properties.javax.persistence.validation.mode=none)
Even if I disable Hibernate validator, my test cases fail because something prevent Spring to use the default string interpolation for validation messages that should be something like [Constraint].[class name lowercase].[propertyName]. I don't want to use the constraint annotation with the value element like this #NotBlank(message="{mycustom.message}") because I don't see the point considering that has his own convetion for interpolation and I can take advantage of that...that means less coding.
I attach the code; you can just run Junit tests and see errors (Hibernate validation is enable, check application.properties).
What am I doing wrong? What could I do to solve those two problems?
====== UPDATE ======
Just to clarify, reading Spring validation documentation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints they say:
By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This allows your custom ConstraintValidators to benefit from dependency injection like any other Spring bean.
As you can see, a ConstraintValidator implementation may have its dependencies #Autowired like any other Spring bean.
In my configuration class I created my LocalValidatorFactoryBean as they write.
Another interesting questions are this and this, but I had not luck with them.
====== UPDATE 2 ======
After a lot of reseach, seems with Hibernate validator the injection is not provided.
I found a couple of way you can do that:
1st way
Create this configuration class:
#Configuration
public class HibernateValidationConfiguration extends HibernateJpaAutoConfiguration {
public HibernateValidationConfiguration(DataSource dataSource, JpaProperties jpaProperties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers);
}
#Autowired
private Validator validator;
#Override
protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
super.customizeVendorProperties(vendorProperties);
vendorProperties.put("javax.persistence.validation.factory", validator);
}
}
2nd way
Create an utility bean
#Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext context;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
and then in the validator initialization:
#Override
public void initialize(ValidUser constraintAnnotation) {
userRepository = BeanUtil.getBean(UserRepository.class);
em = BeanUtil.getBean(EntityManager.class);
}
very important
In both cases, in order to make the it works you have to "reset" the entity manager in this way:
#Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
em.setFlushMode(FlushModeType.COMMIT);
//your code
} finally {
em.setFlushMode(FlushModeType.AUTO);
}
}
Anyway, I don't know if this is really a safe way. Probably it's not a good practice access to the persistence layer at all.
If you really need to use injection in your Validator try adding #Configurable annotation on it:
#Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
#Autowired
private UserRepository userRepository;
// this initialize method wouldn't be needed if you use HV 6.0 as it has a default implementation now
#Override
public void initialize(ValidUser constraintAnnotation) {
}
#Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername( value.getUsername() );
if ( foundUser != null && foundUser.getId() != value.getId() ) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate( "{ValidUser.unique.username}" ).addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error( "", e );
return false;
}
return true;
}
}
From the documentation to that annotation:
Marks a class as being eligible for Spring-driven configuration
So this should solve your null problem. To make it work though, you would need to configure AspectJ... (Check how to use #Configurable in Spring for that)
I am integrating Spring4 and Hibernate5, but there is a problem that I can't resolve.
I use #EntityListener annotation on the BaseEntity class that is a super class for other business model.
Also I use #MappedSuperclass on the BaseEntity.
But it don't work!
Use Spring base annotation and run application successfully.
Also I inserted a record to db.
So I think my configuration of project is current.
Any body let me know why?
Thanks very much.
This is BaseEntity class.
#MappedSuperclass
#EntityListeners(EntityListener.class)
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, updatable = false)
private Date createDate;
#Column(nullable = false)
private Date modifyDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
This is EntityListener class.
public class EntityListener {
#PrePersist
public void prePersist(BaseEntity entity) {
entity.setCreateDate(new Date());
entity.setModifyDate(new Date());
}
#PreUpdate
public void preUpdate(BaseEntity entity) {
entity.setModifyDate(new Date());
}
}
The following is my project configuration base on Spring annotation.
#Configuration
#EnableWebMvc
//#ImportResource({ "classpath:xxxxx.xml" })
#PropertySources({
#PropertySource("classpath:application.properties")
})
#ComponentScan({"com.yeager.admin.persistence","com.yeager.admin.web","com.yeager.admin.service","com.yeager.admin.common"})
#EnableAspectJAutoProxy
//#EnableRetry
public class AppConfig {
#Bean(name = "multipartResolver")
public CommonsMultipartResolver getResolver() throws IOException {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
return resolver;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public static SpringContext springContext() {
return new SpringContext();
}
}
The main configuration about DAL like this,
#Configuration
#EnableTransactionManagement
#PropertySource({"classpath:persistence-mysql.properties"})
public class PersistenceConfig {
#Autowired
private Environment env;
public PersistenceConfig() {
super();
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.yeager.admin.persistence.entity");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
try {
comboPooledDataSource.setDriverClass(env.getProperty("jdbc.driver"));
} catch (PropertyVetoException e) {
e.printStackTrace();
}
comboPooledDataSource.setJdbcUrl(env.getProperty("jdbc.url"));
comboPooledDataSource.setUser(env.getProperty("jdbc.username"));
comboPooledDataSource.setPassword(env.getProperty("jdbc.password"));
comboPooledDataSource.setInitialPoolSize(Integer.valueOf(env.getProperty("datasource.pool.initialPoolSize")));
return comboPooledDataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private final Properties hibernateProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.generate_statistics",env.getProperty("hibernate.generate_statistics"));
hibernateProperties.setProperty("hibernate.jdbc.fetch_size", env.getProperty("hibernate.jdbc.fetch_size"));
hibernateProperties.setProperty("hibernate.jdbc.batch_size", env.getProperty("hibernate.jdbc.batch_size"));
hibernateProperties.setProperty("hibernate.max_fetch_depth", env.getProperty("hibernate.max_fetch_depth"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache",env.getProperty("hibernate.cache.use_second_level_cache"));
hibernateProperties.setProperty("hibernate.cache.use_query_cache",env.getProperty("hibernate.cache.use_query_cache"));
// hibernateProperties.setProperty("hibernate.cache.provider_class",env.getProperty("hibernate.cache.provider_class"));
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");
return hibernateProperties;
}
}
I use LocalSessionFactoryBean class of Hibernate rather than EntityManager class of JPA. I wonder if this cause ?
--------------- 6.19 --------------
I am wrong. I don't should use #EntityListener annotation base on Spring LocalSessionFactoryBean class.
For hibernate5, there is a special configuration way.
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
Now, I modify my code as following,
#Component
public class EntityEventListener {
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
public void registerListeners(){
EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.PRE_INSERT, PreInsertEntityListener.class);
}
}
PreInsertEntityListener
public class PreInsertEntityListener implements PreInsertEventListener {
#Override
public boolean onPreInsert(PreInsertEvent event) {
// if (event.getEntity() instanceof AdminUser){
// ((AdminUser) event.getEntity()).setCreateDate(new Date());
// ((AdminUser) event.getEntity()).setModifyDate(new Date());
// }
BaseEntity baseEntity = (BaseEntity) event.getEntity();
baseEntity.setCreateDate(new Date());
baseEntity.setModifyDate(new Date());
return false;
}
}
But, I have a other problem.
I read hibernate doc and search many information about this. My code don't work already when I insert entity data.
Please help me, thanks!
Although you did neither post the concrete / derived entity nor the business code to persist it, the code you posted seems correct.
For giving it a small test I added a generated UID to the super class and created a concrete entity:
import javax.persistence.Entity;
#Entity
public class DerivedEntity extends BaseEntity {
private static final long serialVersionUID = -6441043639437893962L;
}
And since you mentioned Spring, here is a Spring Data JPA repository to save it:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface DerivedEntityRepository extends CrudRepository<DerivedEntity, Long> {
}
This small test should show that the (#PrePersist) listener works:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
#RunWith(SpringRunner.class)
#Transactional
#SpringBootTest
public class DerivedEntityRepositoryTests {
#Autowired
private DerivedEntityRepository derivedEntityRepository;
#Test
public void insertDerivedEntity() {
DerivedEntity entity = new DerivedEntity();
entity = derivedEntityRepository.save(entity);
assertThat(entity.getCreateDate()).isNotNull();
}
}
And just to mention it, if you don't want to enhance your custom listener in future, the existing Spring Data JPA AuditingEntityListener does exactly what you are doing at the moment (and even more). In this case you could just enhance a #Configuration class with #EnableJpaAuditing and modify your BaseEntity as following:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
// ...
#CreatedDate
#Column(nullable = false, updatable = false)
private Date createDate;
#LastModifiedDate
#Column(nullable = false)
private Date modifyDate;
// ...
}
That would make your custom EntityListener dispensable.
Just take a look Spring JPA Auditing for more information. If you want to enhance auditing with Hibernate, try Hibernate Envers.
I ran into this same issue and in my case the listener defined with #EntityListeners was referring to class (not in the same classloader) in another package and it wasn't being scanned. After adding the class to my persistence context it began working as expected.
So always be sure that any classes related to the persistence are added to the persistence context.
Thanks very much for everyone. I have resolved this problem.
I will share my solution, hope it's helpful for you if you are doing same things.
First, my starting point is wrong. Because I use JPA before, so I use acquiescently #EntityListener annotation when I integrate Spring4 and Hibernate5.
Then, I read Hibernate doc and many relevant article and found there is a new way to implement entity listener. See hibernate doc
Finally, my solution is following.
This is my BaseEntity class.
#MappedSuperclass
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, updatable = false)
private Date createDate;
#Column(nullable = false)
private Date modifyDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
First of all, you need to define EntityListener class.
public class EntityListener implements PreInsertEventListener, PreUpdateEventListener {
private static final String CREATE_DATE_PROPERTY = "createDate";
private static final String MODIFY_DATE_PROPERTY = "modifyDate";
#Override
public boolean onPreInsert(PreInsertEvent event) {
if (event.getEntity() instanceof BaseEntity){
//property name of entity
String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
//property value of entity
Object[] state = event.getState();
for (int i = 0; i < propertyNames.length ; i ++) {
if (CREATE_DATE_PROPERTY.equals(propertyNames[i]) || MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
state[i] = new Date();
}
}
}
return false;
}
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
if (event.getEntity() instanceof BaseEntity){
//property name of entity
String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
//property value of entity
Object[] state = event.getState();
for (int i = 0; i < propertyNames.length ; i ++) {
if (MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
state[i] = new Date();
}
}
}
return false;
}
}
Last, you should register entity event listener.
#SuppressWarnings("unchecked")
#Component
public class EntityEventListenerRegistry {
#Autowired
private SessionFactory sessionFactory;
/**
* EventListenerRegistry:http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
*/
#PostConstruct
public void registerListeners(){
EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.PRE_INSERT, EntityListener.class);
eventListenerRegistry.prependListeners(EventType.PRE_UPDATE, EntityListener.class);
}
}
I have the following code which attempts to save a POJO object (Actor) into MongoDB using Spring Mongo Repository, but the repository object is always Null. I have followed multiple examples but mainly this one
The POJO class:
#Document(collection = "actors")
public class Actor
{
#Id
private String id;
...
//constructor
//setters & getters
}
The repository:
public interface ActorRepository extends MongoRepository<Actor, String>
{
public Actor findByFNameAndLName(String fName, String lName);
public Actor findByFName (String fName);
public Actor findByLName(String lName);
}
The service that uses the repository:
#Service
public class ActorService
{
#Autowired
private ActorRepository actorRepository;
public Actor insert(Actor a)
{
a.setId(null);
return actorRepository.save(a);
}
}
And I access the service from a REST controller class:
#RestController
public class Controllers
{
private static final Logger logger = Logger.getLogger(Controllers.class);
private static final ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringMongoConfig.class);
private ActorService actorService = new ActorService();
#RequestMapping(value="/createActor", method=RequestMethod.POST)
public #ResponseBody String createActor(#RequestParam(value = "fName") String fName,
#RequestParam(value = "lName") String lName,
#RequestParam(value = "role") String role)
{
return actorService.insert(new Actor(null,fName,lName,role)).toString();
}
...
}
The error that I get is NullPointerException from this line: return actorRepository.save(a); in the ActorService.insert() method.
Any Idea why is this happening?
EDIT: Here is the Spring Configurations
#Configuration
public class SpringMongoConfig extends AbstractMongoConfiguration
{
#Bean
public GridFsTemplate gridFsTemplate() throws Exception
{
return new GridFsTemplate(mongoDbFactory(), mappingMongoConverter());
}
#Override
protected String getDatabaseName()
{
return "SEaaS";
}
#Override
#Bean
public Mongo mongo() throws Exception
{
return new MongoClient("localhost" , 27017 );
}
public #Bean MongoTemplate mongoTemplate() throws Exception
{
return new MongoTemplate(mongo(), getDatabaseName());
}
}
The problem is that you are not using Spring to get the ActorService dependency -instead you have manually instantiated the dependency using
private ActorService actorService = new ActorService();.
The following code is the easiest fix in order to inject the ActorService dependency into the controller.
#RestController
public class Controllers
{
private static final Logger logger = Logger.getLogger(Controllers.class);
private static final ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringMongoConfig.class);
#Autowired
private ActorService actorService;
#RequestMapping(value="/createActor", method=RequestMethod.POST)
public #ResponseBody String createActor(#RequestParam(value = "fName") String fName,
#RequestParam(value = "lName") String lName,
#RequestParam(value = "role") String role)
{
return actorService.insert(new Actor(null,fName,lName,role)).toString();
}
...
}