Jpa + Spring - automatically setting transient field value after read from DB - spring

what's the best solution to set a value for a field marked #Transient after the entity has been read from the data source?
I'm using EclipseLink and I'm trying the DescriptorEventAdapter with his postBuild event solution because I need also to get the default value using a Spring bean (obviuosly using DI), but I would know if there is any simpler solution that I'm missing.
Thanks in advance

Here's the simple approach if you're using a repository or DAO:
#Repository
class YourRepository {
#Autowired
private Bean bean;
#PersistenceContext
private EntityManager entityManager;
#Transactional(readOnly = true)
public YourEntity find(..) {
YourEntity entity = lookupUsingEntityManager();
entity.transientField = bean.getDefaultValue();
return entity;
}
}
Here's another approach if you are using active record -style entities:
#Entity
class YourEntity {
#Transient
public Object field;
#PostLoad
public void populateField() {
field = new BeanHolder().bean.getDefaultValueForField();
}
#Configurable
private static class BeanHolder {
#Autowired private Bean bean;
}
}
Mind the semi-pseudo-code. Note that the latter approach works only if you use compile- or load-time AspectJ weaving with <context:spring-configured />.

You got entity which has transient field and the value is always taken from service using DI?
What is the purpose of the field? It's used for some calculation within any entity method?
Such calculation should probably use service's method to obtain the value.
As value from any service is used, I'm not sure whether such calculation (method) belong into entity.
Note that entity and service has completely different lifecycle. The value is changing in the time so it does not make the sense to supply the value in entity's factory at the beginning of it's life?

Related

Spring Data Redis. JPA Repository findBy sometimes fails to fetch existing record

I see some weird case. Sometimes my findBy...() method returns null instead of some object inserted and fetched successfully before. After that the needed object fetches fine. In other words sometimes the search is not working.
Spring Boot edition: 1.5.2.RELEASE
spring-boot-starter-data-redis: 1.5.22.RELEASE
"maxmemory-policy" setting is set to "noeviction"
My obj declaration:
#RedisHash("session")
public class Session implements Serializable {
#Id
private String id;
#Indexed
private Long internalChatId;
#Indexed
private boolean active;
#Indexed
private String chatId;
}
JPA Repository:
#Repository
public interface SessionRepository extends CrudRepository<Session, String> {
Session findByInternalChatIdAndActive(Long internalChatId, Boolean isActive);
}
Redis config:
#Bean
public LettuceConnectionFactory redisConnectionFactory(
RedisProperties redisProperties) {
return new LettuceConnectionFactory(
redisProperties.getRedisHost(),
redisProperties.getRedisPort());
}
#Bean
public RedisTemplate<?, ?> redisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
return template;
}
Thanx in advance for any assist.
We have recently seen similar behavior. In our scenario, we can have multiple threads that read and write to the same repository. Our null return occurs when one thread is doing a save to an object while another is doing a findById for that same object. The findById will occasionally fail. It appears that the save implementation does a delete followed by an add; if the findById gets in during the delete, the null result is returned.
We've had good luck so far in our test programs that can reproduce the null return using a Java Semaphore to gate all access (read, write, delete) to the repository. When the repository access methods are all gated by the same semaphore, we have not seen a null return. Our next step is to try adding the synchronized keyword to the methods in the class that access the repository (as an alternative to using the Semaphore).
This should not happen I don't what is reason. But you can use Option class and if it returns null at least you can avoid exception.
Something like:
Optional<Session> findByInternalChatIdAndActive(Long internalChatId, Boolean isActive);

Database default field not retrieved when using #Transactional

I have the following simple entity FileRegistry :
#Getter
#Setter
#Entity
#ToString
#Table(name = "file_store")
public class FileRegistry {
#Id
private String name;
/**
* Creation timestamp of the registry
* This value is automatically set by database, so setter method
* has been disabled
*/
#Setter(AccessLevel.NONE)
#Column(insertable = false, updatable = false)
private LocalDateTime creationDate;
}
The following FileRepository DAO:
#Repository
public interface FileRepository extends JpaRepository<FileRegistry, String> { }
and the following Spring Boot test :
#SpringBootTest(classes=PersistTestConfig.class, properties = { "spring.config.name=application,db"})
#ActiveProfiles("test")
#Transactional
public class FileRepositoryTest {
#Autowired
FileRepository fileRepository;
#Test
void insertFileTest() {
assertNotNull(fileRepository, "Error initializing File repository");
// Check registry before insertion
List<FileRegistry> allFiles = fileRepository.findAll();
assertNotNull(allFiles, "Error retrieving files from registry");
assertThat(allFiles.size(), is(0));
// Insert file
FileRegistry fileRegistry = new FileRegistry();
fileRegistry.setName("Test");
fileRepository.save(fileRegistry);
// Check that the insertion was successful
allFiles = fileRepository.findAll();
assertNotNull(allFiles, "Error retrieving files from registry");
assertThat(allFiles.size(), is(1));
assertEquals("File registry name mismatch", "Test", allFiles.get(0).getName());
System.out.println(allFiles.get(0));
}
}
Persistence configuration class defined as follows :
#Configuration
#EnableAutoConfiguration
#EnableTransactionManagement
#EnableJpaRepositories
public class PersistTestConfig {
}
The table file_store defined in H2 as :
CREATE TABLE file_store (name VARCHAR NOT NULL, creation_date TIMESTAMP(3) DEFAULT NOW() NOT NULL, CONSTRAINT file_store_pk PRIMARY KEY (name));
Everything works fine except that when I use #Transactional at test level (mainly to benefit from rollbacks i.e. db cleanup on each test) a null value is fetched for the creationDate field :
FileRegistry(name=Test, creationDate=null)
When I remove #Transactional from the test class, the fetched value contains the date as computed by H2 :
FileRegistry(name=Test, creationDate=2019-03-07T17:08:13.392)
I've tried to flush and merge manually the instance to no avail. To be honest, right now I'm a little bit lost on how #Transactional really works, in fact reading the docs and inspecting the code, the underlying JpaRepository implementation (SimpleJpaRepository) is annotated as #Transactional(readOnly = true).
A little help on this subject would be very appreciated.
Ok, figured it out.
Simply issuing a refresh entityManager.refresh(allFiles.get(0)); solves the issue.
I tested also using Hibernate's #Generated(INSERT) specific annotation in the entity creationDate field and it also worked fine.
By the way I've eventually decided to drop this thing in favor of using Spring Data's JpaAuditing features and annotating the field with #CreatedDate annotation to fill the value instead of relying on DB date (by the way, production-wise, you probably shouldn't rely on DB time). To me this is feels more, let's say, "correct" and springy way of doing things.

Is this design of a Spring JPA DAO bad or improper?

I have been working to generalize the methods of the DAO for a project using Spring, JPA and Hibernate. However, I am still very much learning Spring, Java, and coding in general.
Is the below design bad or perfectly fine? Is there a better way to accomplish the same thing? Any advice would be greatly appreciated.
I have simplified the class:
#Repository
public class TestRepository
{
#PersistenceContext
private EntityManager entityManager;
public List<?> getListResults(Class<?> dtoClass, String sqlString)
{
List<?> returnList = null;
Query query = entityManager.createNativeQuery(sqlString, dtoClass);
try
{
returnList = (List<?>) query.getResultList();
}
catch (Exception e)
{
}
return returnList;
}
}
Spring Data JPA is the must convenient way in order to interact with your databases because it helps you to avoid the common mistakes that occurs when you try to configure your ORM mapping, entityManager, transacctionManager and all the rest of necessary components in order to establish a communication between your entity domains and your database.
For example you have a pojo like this:
#Entity
public class Item {
#Id
private Long id;
......
}
You can create an interface in order to get or put information to the item repository like this:
public interface ItemRepository extends from JpaRepository<Item,Long>{}
When you need to save the Item just #Autowired the ItemRepository, this is the must important part because the previous interface that is created without methods now exposes ready-to-work methods that will interact with your database, this is the abstraction level that makes Spring Data JPA very useful:
#Autowired
ItemRepository itemRepo
public void createItem(){
Item item = new Item();
itemRepo.save(item);
//or you can get information
List<Item> itemList = itemRepo.findAll();
}
More information in Spring Data JPA Documentation
How about using Spring Data Repositories?
#Repository
public interface SomethingRepository extends JpaRepository<Something, Long> {
}
That way you get lots of methods without having to manually write your SQL query as a string, you retain type safety and you can leverage the power of JPA queries and dynamic proxies that do this whole SQL business for you.

Using #Configurable domain object properties to assign a specific behaviour based on data

I have a domain object which looks like this.
i need to use the data fetched from the database ("type" in this example) to fetch and inject the correct type of service.
I get this output which means that the DB data are not set during the call :
entity is a bean postconstruct: PocProduct [id=null, type=null, productName=null].. attching behavior!
I get the same resilt when I try with the initializing Bean.
What is the correct way to configure this?
#Entity
#Table(name = "AAA_POC_PROD")
#Configurable(dependencyCheck = true)
#Scope("prototype")
public class PocProduct implements Serializable, InitializingBean {
/**
*
*/
private static final long serialVersionUID = 1136936011238094989L;
#Id
private String id;
private String type;
private String productName;
#Transient
private Behaviour behaviour;
#Transient
#Autowired
private BehaviourFactory behaviourFactory;
//getters and setters
#PostConstruct
public void attachBehavior() {
System.out.println("entity is a bean postconstruct: " + this + ".. attching behavior!");
//Need to call this : depends on type which is fetched from DB
// this.behaviour = behaviourFactory.getTypeBasedBehaviour(type);
}
}
Configurable beans are initialized by Spring after or before construction, depending on the value of the #Configurable.preConstruction attribute. When loading an entity from a database this means the following sequence of events:
The JPA provider creates the entity by invoking it's constructor via reflection
While the constructor executes, spring-aspects' AnnotationBeanConfigurerAspect intercepts the constructor execution and, before (or after) the constructor executes, Spring will configure this newly created object by executing any bean configuration you have in your spring context, including autowiring of properties.
The JPA provider will receive this object already configured by Spring and will start populating its persistent properties with data fetched from the DB.
Optionally, if you set up #PostLoad methods, the JPA provider will invoke these methods so that your entities have a chance to do work after the entity is fully populated by data from the DB.
From what I see you're trying to do, this 4th step is where you should put your custom behavior logic, assuming everything else is working properly.

How to explictly state that an Entity is new (transient) in JPA?

I am using a Spring Data JpaRepository, with Hibernate as JPA provider.
Normally when working directly with Hibernate, the decision between EntityManager#persist() and EntityManager#save() is up to the programmer. With Spring Data repositories, there is only save(). I do not want to discuss the pros and cons here. Let us consider the following, simple base class:
#MappedSuperclass
public abstract class PersistableObject {
#Id
private String id;
public PersistableObject(){
this.id = UUID.randomUUID().toString();
}
// hashCode() and equals() are implemented based on equality of 'id'
}
Using this base class, the Spring Data repository cannot tell which Entities are "new" (have not been saved to DB yet), as the regular check for id == null clearly does not work in this case, because the UUIDs are eagerly assigned to ensure the correctness of equals() and hashCode(). So what the repository seems to do is to always invoke EntityManager#merge() - which is clearly inefficient for transient entities.
The question is: how do I tell JPA (or Spring Data) that an Entity is new, such that it uses EntityManager#persist() instead of #merge() if possible?
I was thinking about something along these lines (using JPA lifecycle callbacks):
#MappedSuperclass
public abstract class PersistableObject {
#Transient
private boolean isNew = true; // by default, treat entity as new
#PostLoad
private void loaded(){
// a loaded entity is never new
this.isNew = false;
}
#PostPersist
private void saved(){
// a saved entity is not new anymore
this.isNew = false;
}
// how do I get JPA (or Spring Data) to use this method?
public boolean isNew(){
return this.isNew;
}
// all other properties, constructor, hashCode() and equals same as above
}
I'd like to add one more remark here. Even though it only works for Spring Data and not for general JPA, I think it's worth mentioning that Spring provides the Persistable<T> interface which has two methods:
T getId();
boolean isNew();
By implementing this interface (e.g. as in the opening question post), the Spring Data JpaRepositories will ask the entity itself if it is new or not, which can be pretty handy in certain cases.
Maybe you should add #Version column:
#Version
private Long version
in the case of new entity it will be null

Resources