Spring ignores #Transactional annotation - java

on one of our projects we encountered a problem with Spring ignoring #Transactional annotation and then failing with the following error.
Error starting ApplicationContext. To display the conditions report
re-run your application with 'debug' enabled. 2018-09-13 15:05:18,406
ERROR [main] org.springframework.boot.SpringApplication Application
run failed org.springframework.dao.InvalidDataAccessApiUsageException:
No EntityManager with actual transaction available for current thread
- cannot reliably process 'remove' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with
actual transaction available for current thread - cannot reliably
process 'remove' call at
com.my.service.CacheAService.deleteShortTermCache(CacheAService.java:70)
~[classes/:na]
I found similar questions but none of the solutions applied to this case.
#EnableTransactionManagement is present
Transactional class implements an interface
Transactional method is public
Transactional method is not called internally
When I annotate CacheService with #Transactional, everything works again. But I am trying to understand why would Spring ignore #Transactional on CacheAService.
I tried logging Spring's transaction interceptor but there is no mention of CacheA. This is the only related thing that gets logged.
2018-09-13 15:05:18,242 TRACE [main]
org.springframework.transaction.interceptor.TransactionInterceptor
Don't need to create transaction for
[org.springframework.data.jpa.repository.support.SimpleJpaRepository.deleteByValidity]:
This method isn't transactional.
Here is the simplified code. Code is invoked during application startup by Spring's ContextRefreshedEvent.
#Service
public class CacheService implements Cache {
#Autowired
private CacheA cacheAService;
#Autowired
private CacheB cacheBService;
#Override
public void clearCache() {
cacheAService.deleteShortTermCache();
cacheBService.deleteAll();
}
}
public interface CacheA {
void deleteShortTermCache();
}
#Service
#Transactional(readOnly = true)
public class CacheAService implements CacheA {
#Autowired
private CacheARepository cacheARepository;
#Override
#Transactional
public void deleteShortTermCache() {
cacheARepository.deleteByValidity(CacheValidity.SHORT_TERM);
}
}
public interface CacheB {
void deleteAll();
}
#Service
#Transactional(readOnly = true)
public class CacheBService implements CacheB {
#Autowired
private CacheBRepository cacheBRepository;
#Override
#Transactional
public void deleteAll {
cacheBRepository.deleteAll();
}
}
public enum CacheValidity {
SHORT_TERM,
LONG_TERM
}
#Repository
public interface CacheARepository extends JpaRepository<CacheItem, Integer> {
void deleteByValidity(CacheValidity validity);
}
public enum CacheItemKey {
AVAILABLE,
FUTURE,
AVAILABLE_UTM,
FUTURE_UTM,
REGION
}
#Entity
#Table(name = "cache_item")
public class CacheItem {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cache_item_id_seq")
#SequenceGenerator(name = "cache_item_id_seq", sequenceName = "cache_item_id_seq", allocationSize = 1)
private Integer id;
#Column(nullable = false, unique = true)
#Enumerated(EnumType.STRING)
private CacheItemKey key;
#Column(nullable = false)
private String value;
#Column(name = "date_modified", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date dateModified;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private CacheValidity validity;
public Integer getId() {
return id;
}
public void setId(final Integer id) {
this.id = id;
}
public CacheItemKey getKey() {
return key;
}
public void setKey(final CacheItemKey key) {
this.key = key;
}
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
public Date getDateModified() {
return dateModified;
}
public void setDateModified(final Date dateModified) {
this.dateModified = dateModified;
}
public CacheValidity getValidity() {
return validity;
}
public void setValidity(final CacheValidity validity) {
this.validity = validity;
}
}
Edit:
After some digging I found this in the logs.
2018-09-14 06:24:11,174 INFO [localhost-startStop-1]
org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker
Bean 'cacheAService' of type [com.my.service.CacheAService] is not
eligible for getting processed by all BeanPostProcessors (for example:
not eligible for auto-proxying)

We discovered that this issue was caused by Spring Boot's auto-configuration. Since auto-configuration already setups the Transaction management, our custom configuration of #EnableTransactionManagement broke the instantiation of transaction advisors. Removing of #EnableTransactionManagement from our configuration solves the issue.

Try to use only one Transactional annotation (in class or method). May be, the problem is with #Transactional(readOnly = true), because you transaction isn't readOnly, I can't sure what Transactional annotation is preferred by Spring. Try to use:
#Service
public class CacheAService implements CacheA {
#Autowired
private CacheARepository cacheARepository;
#Override
#Transactional
public void deleteShortTermCache() {
cacheARepository.deleteByValidity(CacheValidity.SHORT_TERM);
}
}
or
#Service
#Transactional
public class CacheAService implements CacheA {
#Autowired
private CacheARepository cacheARepository;
#Override
public void deleteShortTermCache() {
cacheARepository.deleteByValidity(CacheValidity.SHORT_TERM);
}
}

I ran in a familiar situation. For those, who have same problems but the answers won't work.
The #Transactional-annotation works per default only on public-methods. Not on private and not on package-private.
See this question for more information and workarounds -> Does Spring #Transactional attribute work on a private method?

Related

Am I missing something if I use my entity class without #Id in Spring Data JDBC?

I am new to spring.
I just tried successfully using an entity class without #Id in Spring Data JDBC
Custom query was added in my repository for retrieving data from 2 mysql tables and returning an entity having the joined table data.
If I plan to use only custom queries, am I missing anything here?
Here's my entity class without #Id or #Entity:
public class Item
{
private long id;
private String code;
private String itemName;
private String groupName;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
}
Repository layer:
#Repository
public interface ItemRepository extends PagingAndSortingRepository<Item, Long>
{
#Query("SELECT a.id, a.code, a.name AS item_name,
b.name as group_name from item a, item_group b
WHERE a.group_id = b.id AND a.id=:id")
Item findItemById(#Param("id") Long id);
}
Service layer:
#Service
public class ItemServiceImpl implements ItemService
{
private final ItemRepository itemRepository;
public ItemServiceImpl(ItemRepository itemRepository)
{
this.itemRepository = itemRepository;
}
#Override
#Transactional(readOnly=true)
public Item findItemById(Long id)
{
return itemRepository.findItemById(id);
}
}
My updated main Configuration class in response to answer of Jens:
#SpringBootApplication
#EnableJdbcRepositories
public class SpringDataJdbcApplication extends AbstractJdbcConfiguration
{
public static void main(String[] args)
{
SpringApplication.run(SpringDataJdbcApplication.class, args);
}
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource()
{
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
return dataSourceBuilder.build();
}
#Bean
NamedParameterJdbcOperations namedParameterJdbcOperations(DataSource dataSource)
{
return new NamedParameterJdbcTemplate(dataSource);
}
#Bean
PlatformTransactionManager transactionManager()
{
return new DataSourceTransactionManager(dataSource());
}
}
If you don't get any exceptions you should be fine. There shouldn't be anything in Spring Data JDBC that silently breaks when the id is not specified.
The problem is though: I don't consider it a feature that this works, but just accidental behaviour. This means it might break with any version, although replacing these methods with custom implementations based on a NamedParameterJdbcTemplate shouldn't be to hard, so the risk is limited.
The question though is: Why don't you add the #Id annotation, after all your entity does have an id. And the whole idea of a repository conceptually requires an id.
If it's working and you really don't want to use the annotations, you can do it. But I think that it's unnecessary complication. You can expect errors that would not be there if you had used the annotations and code will be harder to debug. If you are new in Spring I recommend to use annotations. But after all it depend on you how will you design your applications. For sure advantage of approach without annotations is higher control about database.

Hibernate is not using the proxy of a session scope bean spring

I'm using Spring #Scope(value = "session", proxyMode=ScopedProxyMode.TARGET_CLASS) beans for objects that should be shared across a single Http-Session. This will provide for example one "Project" object for each User who is using my application.
To get this working I had to implement an interceptor for Hibernate that is returning the name of the class:
public class EntityProxySupportHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 7470168733867103334L;
#Override
public String getEntityName(Object object) {
return AopUtils.getTargetClass(object).getName();
}
}
With this interceptor I can use a Spring CrudRepository to save a Project-entity in the database:
#Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
Project findByProjectId(int projectId);
}
Project-entity:
#Component
#Entity
#Table(name = "xxx.projects")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Project implements Serializable {
private static final long serialVersionUID = -8071542032564334337L;
private int projectId;
private int projectType;
#Id
#Column(name = "project_id")
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
#Column(name = "project_type")
public int getProjectType() {
return projectType;
}
public void setProjectType(int projectType) {
this.projectType = projectType;
}
}
Storing the Project in the database works as expected. I can have a look at the database and the correct values are inserted. Now I have a different entity that I'm creating the same way as the project and that I want to save in the database via a CrudRepository.
Here the problem begins. Hibernate is not inserting the values that I have set. Hibernate always only inserts null into the database. Reading the values in my Spring application is working as expected. I think that Hibernate is not using the proxy of the entity but the underlying blueprint of the object. How can I force Hibernate to use the proxy with the correct values?
Repository:
#Repository("DataInput001Repository")
public interface DataInputRepository extends CrudRepository<DataInput, DataInputId> {}
Entity:
#Component("DataInput001")
#Entity
#Table(name = "xx.data_input_001")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#IdClass(DatanputId.class)
public class DataInput implements Serializable {
private static final long serialVersionUID = -6941087210396795612L;
#Id
#Column(name = "project_id")
private int projectId;
#Column(name = "income")
private String income;
#Column(name = "income_increase")
private String incomeIncrease;
/* Getter + Setter */
}
Service:
#Service("DataInputService001")
public class DataInputServiceImpl implements DataInputService {
#Resource(name = "DataInputMapper001")
DataInputMapperImpl dataInputMapper;
#Resource(name = "DataInput001Repository")
DataInputRepository dataInputRepository;
#Resource(name = "DataInput001")
DataInput datanInput;
#Transactional
public void createDataInput(String json) throws Exception {
dataInputMapper.mapDataInput(json);
dataInputRepository.save(dataInput);
}
public DataInput getDataInput() {
return dataInput;
}
public void setDataInput(DataInput dataInput) {
this.dataInput = dataInput;
}
}

Hibernate: generated Id remains in a not persistent entity after SQLIntegrityConstraintViolationException

Hello, everybody!
Some time ago I run into a trouble: if save method of repository fails, identifier, injected to a bean by Hibernate, remains in the bean. That behaviour may led us to a situation, when we will think about our not persistent bean as about persistent one. I would be pleased to know what practice is common to avoid this situation.
Example test(spring boot + hibernate + oracle database):
#Entity
#SequenceGenerator(name = "TEST_ENTITY_GENERATOR", allocationSize = 1, sequenceName = "TEST_ENTITY_SEQ")
public class TestEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEST_ENTITY_GENERATOR")
private Long id;
#Column(nullable = false)
private String name;
public Long getId() {
return id;
}
}
#Repository
public interface TestEntityRepository extends JpaRepository<TestEntity, Long> {
}
#RunWith(SpringRunner.class)
#SpringBootTest
public class RemainingIdTest {
#Autowired
private TestEntityRepository testEntityRepository;
#Test
public void test() {
TestEntity entity = new TestEntity();
try {
Assertions.assertThat(entity.getId()).isNull();
testEntityRepository.save(entity);
Assertions.fail("Save must fail");
} catch (DataIntegrityViolationException e) {
Assertions.assertThat(entity.getId()).isNotNull();
}
}
}
A possible solution is to use org.hibernate.event.spi.PreInsertEventListener where we can bind the transaction with a processor that will clear your entity if transaction is failed.
Example:
#Component
public class IdentifierCleaner implements PreInsertEventListener {
#Autowired
private EntityManagerFactory entityManagerFactory;
#PostConstruct
private void init() {
SessionFactoryImpl sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl.class);
EventListenerRegistry registry = sessionFactory.getServiceRegistry().getService(EventListenerRegistry.class);
registry.getEventListenerGroup(EventType.PRE_INSERT).appendListener(this);
}
#Override
public boolean onPreInsert(PreInsertEvent event) {
Object entity = event.getEntity();
event.getSession().getActionQueue().registerProcess(((success, session) -> {
if (!success) {
event.getPersister().resetIdentifier(
entity,
event.getId(),
event.getPersister().getVersion(entity),
event.getSession()
);
}
}));
return false;
}
}

Spring data rest validation for PUT and PATCH running after DB update

I have a SDR project and I am successfully validating the user entity for POST request but as soon as I update an existing entity using either PATCH or PUT the DB is updated BEFORE the validation is executed (the validator is being executed and error is returned but the DB is being updated anyway).
Do I need to setup a separate config for update ? Am I missing an extra step for that?
Entity
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class Member {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_id_gen")
#SequenceGenerator(name = "member_id_gen", sequenceName = "member_id_seq")
#Id
#JsonIgnore
private long id;
#Version
private Integer version;
#NotNull
protected String firstName;
#NotNull
protected String lastName;
#Valid
protected String email;
}
Repository
#RepositoryRestResource(collectionResourceRel = "members", path = "member")
public interface MemberRepository extends PagingAndSortingRepository<Member, Long> {
public Member findByFirstName(String firstName);
public Member findByLastName(String lastName);
}
Validator
#Component
public class BeforeSaveMemberValidator implements Validator {
public BeforeSaveMemberValidator() {}
private String EMAIL_REGEX = "^[a-zA-Z0-9._%+-]+#[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6}$";
#Override
public boolean supports(Class<?> clazz) {
return Member.class.equals(clazz);
}
#Override
public void validate(Object target, Errors errors) {
Member member = (Member) target;
if(ObjectUtils.isEmpty(member.getFirstName())) {
errors.rejectValue("firstName", "member.firstName.empty");
}
if(ObjectUtils.isEmpty(member.getLastName())) {
errors.rejectValue("lastName", "member.lastName.empty");
}
if(!ObjectUtils.isEmpty(member.getDni()) && !member.getDni().matches("^[a-zA-Z0-9]*$")) {
errors.rejectValue("dni", "member.dni.invalid");
}
if(!ObjectUtils.isEmpty(member.getEmail()) && !member.getEmail().matches(EMAIL_REGEX)) {
errors.rejectValue("email", "member.email.notValid");
}
}
}
BeforeSave service
#Service
#RepositoryEventHandler(Member.class)
public class MemberService {
#HandleBeforeCreate
#HandleBeforeSave
#Transactional
public void beforeCreate(Member member) {
...
}
}
I think you should rename your validator, for example, to MemberValidator then assign it as described here:
#Override
protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", new MemberValidator());
v.addValidator("beforeSave", new MemberValidator());
}
But I suggest you to use Bean validation instead of your custom validators. To use it in SDR project you can inject LocalValidatorFactoryBean, then assign it for 'beforeCreate' and 'beforeSave' events in configureValidatingRepositoryEventListener:
#Configuration
#RequiredArgsConstructor // Lombok annotation
public class RepoRestConfig extends RepositoryRestConfigurerAdapter {
#NonNull private final LocalValidatorFactoryBean validatorFactoryBean;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) {
v.addValidator("beforeCreate", validatorFactoryBean);
v.addValidator("beforeSave", validatorFactoryBean);
super.configureValidatingRepositoryEventListener(v);
}
}
In this case your SDR will automatically validate payloads of POST, PUT and PATCH requests for all exposed SDR repositories.
See my example for more details.

Spring Data fills #LastModifiedDate by not #CreatedDate

I have following Spring Boot sample application.
The crazy thing is if I add #EnableMongoAuditing annotation on SampleApplication bean, lastModifiedDate would be filled by createDate would not. Why is that? I searched the web and many people had problems on emptying createDate during an update, but I don't have an update.
Document class:
#Document
public class SampleBean implements Persistable<String> {
#Id
public String id;
#CreatedDate
public LocalDateTime createDate;
#LastModifiedDate
public LocalDateTime lastModifiedDate;
public String name;
#Override
public String getId() {
return id;
}
#Override
public boolean isNew() {
return id != null;
}
}
Repository Interface:
#Repository
public interface SampleBeanRepository extends MongoRepository<SampleBean, String> {
}
Rest Controller:
#RestController
public class WebService {
#Autowired
private SampleBeanRepository repository;
#RequestMapping("/insert")
public String insert() {
SampleBean sampleBean = new SampleBean();
sampleBean.name = "Prefix" + new Random().nextInt(1000);
repository.insert(sampleBean);
return "done";
}
#RequestMapping("/")
public Collection<SampleBean> home() {
return repository.findAll();
}
}
Application Config:
#SpringBootApplication
#EnableMongoAuditing
public class ApplicationConfig {
public static void main(String[] args) {
SpringApplication.run(ApplicationConfig.class, args);
}
}
Your isNew() strategy is the culprit here. Since you have set condition as id != null. Everytime your SampleBean is created there will be no id set as per your code snippet, the isNew() method will return as false hence only LastModifiedDate will be set by the framework. Either change the isNew() method condition to return id == null; or just don't implement Persistable interface whatever default strategy for isNew will be picked.

Categories

Resources