Static Metamodel attributes null when unit testing - java

I have #Entity classes in an external package that also have static metamodels. In my application's service class, I am using those metamodels and the EntityManager/CriteriaBuilder/CriteriaQuery to retrieve my data. This works fine when running the application. However, when running unit tests, my metamodels and their attributes are always null.
Code...
package com.example.core.entities;
#Entity
#Table(schema = "lookup", name="BookingSystem")
public class BookingSystem implements ILookupEntity, IAuditEntity, Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id")
public Integer id;
#Column(name = "name")
public String name;
#Column(name = "code")
public Integer code;
}
package com.example.core.entities;
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(BookingSystem.class)
public abstract class BookingSystem_ {
public static volatile SingularAttribute<BookingSystem, Integer> id;
public static volatile SingularAttribute<BookingSystem, Integer> code;
public static volatile SingularAttribute<BookingSystem, String> name;
}
Usage in my app's service class...
package com.example.bookingsystem;
#Service
public class BookingService {
#PersistenceContext
private EntityManager entityManager;
public void saveBooking(Booking booking) {
//...
RepositoryQueryBuilder<BookingSystem> bookingSystemSelector = new RepositoryQueryBuilder<>(entityManager, BookingSystem.class);
List<BookingSystem> bookingSystems = bookingSystemSelector
.and(BookingSystem_.code, booking.bookingSystem.code) //<-- Here "BookingSystem_.code" is null.
.getResultList();
//...
}
}
The "RepositoryQueryBuilder" class is just a utility builder class that wraps an EntityManager, CriteriaBuilder, etc. Basically modeled after this example...
JPA Criteria Predicate Conditions
Unit test code...
package com.example.bookingsystem;
public abstract class BaseTestSetup {
#InjectMocks
protected BookingService bookingService;
protected EntityManager entityManager = PowerMockito.mock(EntityManager.class);
protected CriteriaBuilder criteriaBuilder = PowerMockito.mock(CriteriaBuilder.class);
protected CriteriaQuery<BookingSystem> criteriaQuery = PowerMockito.mock(CriteriaQuery.class);
protected Root<BookingSystem> root = PowerMockito.mock(Root.class);
protected void arrange() {
when(entityManager.getCriteriaBuilder()).thenReturn(criteriaBuilder);
when(criteriaBuilder.createQuery(BookingSystem.class)).thenReturn(criteriaQuery);
when(criteriaQuery.from(Matchers.<Class<BookingSystem>>any())).thenReturn(root);
when(criteriaQuery.from(Matchers.<EntityType<BookingSystem>>any())).thenReturn(root);
}
}
#RunWith(PowerMockRunner.class)
public class BookingServiceTest extends BaseTestSetup {
#BeforeClass
#Override
public void arrange() {
super.arrange();
//...
}
#Test
public void doIt() {
Booking booking = new Booking();
booking.id = 12345;
booking.bookingSystem = new BookingSystem();
booking.bookingSystem.id = 1;
booking.bookingSystem.code = 106000;
bookingService.saveBooking(booking);
}
}
I've looked at this JPA/Hibernate Static Metamodel Attributes not Populated -- NullPointerException, but the solution seems to be "make sure that the entity and its metamodel are in the same package", but as you can see, both are already in my "com.example.core.entities" package.
I'm using all bean and annotation driven configruation in my code (no persistence or context xml files). As far as testing goes, I'm using TestNG and PowerMock from within IntelliJ.
It just seems as if the metamodels aren't being picked up during unit tests. Any ideas.

The static metamodel classes are populated when hibernate is loaded. So, either you configure hibernate context in your test or you populate the attributes manually before the method execution. In you code, you could do:
#Test
public void doIt() {
BookingSystem_.code = new SingularAttributeMock<BookingSystem, Integer>();
bookingService.saveBooking(booking);
}
}
The class SingularAttributeMock can be created custom-made in order to use it in your tests. You can also use any other implementation of the SingularAttribute class.
public class SingularAttributeMock<X, Y> implements SingularAttribute<X, Y> {
//Overriding methods of SingularAttribute...
}

Instead of creating own class, I suggest making Mockito to do the job for you.
#Mock // declare a mock along the others you might have
private SingularAttribute<BookingSystem, Integer> code;
#Before
public void setUp() throws Exception {
// fill metamodel with it
BookingSystem_.code = code;
}
Worth to mention however that bringing metamodels to service layer is not very good practice, you would rather push them down to DAO methods.

There is no need to manual initialization.
You should observe following rules :
Metamodel classes should declared as abstract class.
Metamodel classes should be in the same package as the entity classes they
describe;
They should have the same name as the entity classes they
describe, followed by an underscore (e.g. Product is the entity,
Product_ is the metamodel class);
If an entity inherits from another
entity or from a mapped superclass, its metamodel class should
inherit from the metamodel class that describes its immediate
superclass (e.g. if SpecialProduct extends Product, which extends
PersistentObject, then SpecialProduct_ should extend Product_ which
should extend PersistentObject_).

In my case, mock the metamodel doesn't worked, so I just get it from entityManager.
private EntityManager entityManager;
#Before
public void init() {
// Assume that entityManager was correctly initialized.
this.configMetaModel();
}
private void configMetaModel() {
Metamodel metamodel = this.entityManager.getMetamodel();
BiFunction<EntityType<MY_ENTITY>, String, Attribute>
bfAttr = (entity, field) -> entity.getAttributes()
.stream()
.filter(a -> a.getName().equals(field))
.findAny()
.get();
Function<Attribute, SingularAttribute<MY_ENTITY, String>> fToStr = attribute ->
(SingularAttribute<MY_ENTITY, String>) attribute;
Function<Attribute, SingularAttribute<MY_ENTITY, LocalDate>> fToDate = attribute ->
(SingularAttribute<MY_ENTITY, LocalDate>) attribute;
EntityType<MY_ENTITY> entity = metamodel.entity(MY_ENTITY.class);
MY_ENTITY_.id = fToStr.apply(bfAttr.apply(entity, "id"));
MY_ENTITY_.name = fToStr.apply(bfAttr.apply(entity, "name"));
MY_ENTITY_.someDate = fToDate.apply(bfAttr.apply(entity, "someDate"));
}
"MY_ENTITY" replace my entity
"MY_ENTITY_" replace my entity metamodel
Once I did this, I could run all my unit test perfectly.

Related

#Version field in entity class not increased during JPA test even though I updated it

I have a simple Repository:
public interface ReviewRepository extends CrudRepository<ReviewEntity, Integer> {
#Transactional(readOnly = true)
List<ReviewEntity> findByProductId(int productId);
}
I want to test it using test containers I followed the procedures and wrote my test case:
public abstract class MySqlTestBase {
private static MySQLContainer database = new MySQLContainer("mysql:5.7.32");
static {
database.start();
}
#DynamicPropertySource
static void databaseProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", database::getJdbcUrl);
registry.add("spring.datasource.username", database::getUsername);
registry.add("spring.datasource.password", database::getPassword);
}
}
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class PersistTests extends MySqlTestBase {
#Autowired
private ReviewRepository repository;
private ReviewEntity savedEntity;
#BeforeEach
void setupDb() {
repository.deleteAll();
ReviewEntity entity = new ReviewEntity(1, 2, "author1");
savedEntity = repository.save(entity);
assertEqualsReview(entity, savedEntity);
}
#Test
void update() {
savedEntity.setAuthor("author2");
repository.save(savedEntity);
ReviewEntity foundEntity = repository.findById(savedEntity.getId()).get();
assertEquals(1, (long)foundEntity.getVersion());
assertEquals("author2", foundEntity.getAuthor());
}
}
my ReviewEntity also is written like:
#Entity
public class ReviewEntity {
#Id #GeneratedValue
private int id;
#Version
private int version;
private int productId;
private int reviewId;
private String author;
public ReviewEntity(int productId, int reviewId, String author) {
this.productId = productId;
this.reviewId = reviewId;
this.author = author;
}
// setter and getter
}
When I run this test it fails at the assertEquals(1, (long)foundEntity.getVersion()); line with this message:
expected: <1> but was: <0>
Expected :1
Actual :0
But I update the ReviewEntity class and according to the documentation the #Version field should automatically increases but this not happens. what part of my test is wrong?
If you look at the default implementation of save method in CrudRepository interface in the SimpleJpaRepository class you will see save method is implemented like:
#Transactional
#Override
public <S extends T> List<S> saveAll(Iterable<S> entities) {
Assert.notNull(entities, "Entities must not be null!");
List<S> result = new ArrayList<S>();
for (S entity : entities) {
result.add(save(entity));
}
return result;
}
meaning it is marked with #Transactional with Required as its propagation level(it is default)
Required propagation works like this:
REQUIRED is the default propagation. Spring checks if there is an
active transaction, and if nothing exists, it creates a new one.
Otherwise, the business logic appends to the currently active
transaction
and for DataJpaTest annotation comment section says:
By default, tests annotated with #DataJpaTest are transactional and
roll back at the end of each test
So for method update in your test a transaction is going to be created and the save method in repository.save(savedEntity); is going to be appended to that transaction. meaning it is committed only if that transaction successfully committed and we now know that's not going to happen.
A workaround for this problem probably would be to annotate test class with #Transactional(propagation = NOT_SUPPORTED) to suspends the currently running transaction then for repository.save(savedEntity); a transaction is going to be created and committed at the end of save method and then you can proceed in your test.

What caused the PersistenceException with the message "detached entity passed to perist"

I'm using:
Quarkus with JPA (javax)
Postgres 11 database
I have:
An Entity
#Entity
#Table(name = "MyEntityTable")
#NamedQuery(name = MyEntity.DOES_EXIST, query = "SELECT x FROM MyEntity x WHERE x.type = :type")
public class MyEntity {
public static final String DOES_EXIST = "MyEntity.DoesExists";
#Id
#SequenceGenerator(name = "myEntitySequence", allocationSize = 1)
#GeneratedValue(generator = myEntitySequence)
private long id;
#Column(name = type)
private String type;
}
A repository
#ApplicationScoped
#Transactional(Transactional.TxType.Supports)
public class MyEntityReporitory {
#Inject
EntityManager entityManager;
#Transactional(Transactional.TxType.Required)
public void persist(final MyEntity entity) {
entityManager.persist(entiy);
}
public boolean doesExist(final String type) {
final TypedQuery<MyEntity> query = entityManager
.createNamedQuery(MyEntity.DOES_EXIST, MyEntity.class)
.setParameter("type", type);
return query.getResultList().size() > 0;
}
}
A test with two variations
Variation 1
#QuarkusTest
#QuarkusTestResource(DatabaseResource.class) // used to set up a docker container with postgres db
public class MyEntityRepositoryTest {
private static final MyEntity ENTITY = entity();
#Inject
MyEntityRepository subject;
#Test
public void testDoesExist() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("type");
assertTrue(actual);
}
#Test
public void testDoesExist_notMatching() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("another_type");
assertFalse(actual);
}
private static MyEntity entity() {
final MyEntity result = new MyEntity();
result.setType("type")
return result;
}
}
When I execute this test class (both tests) I'm getting the following Exception on the second time the persist method is called:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist com.mypackage.MyEntity
...
Variation 2
I removed the constant ENTITY from the test class, instead I'm calling now the entity() method inside the tests, like:
...
subject.persist(entity());
...
at both places. Now the Exeption is gone and everything is fine.
Question
Can someone explain to me, why this is the case (why variante 2 is working and variante 1 not)?
https://vladmihalcea.com/jpa-persist-and-merge/
The persist operation must be used only for new entities. From JPA perspective, an entity is new when it has never been associated with a database row, meaning that there is no table record in the database to match the entity in question.
testDoesExist executed, ENTITY saved to database and ENTITY.id set to 1
testDoesExist_notMatching executed and persist called on ENTITY shows the error beacuse it exists in the database, it has an id assigned
The simplest fix is to call entity() twice, as in you variation 2.
But don't forget that the records will exist after a test is run, and might affect your other test cases. You might want to consider cleaning up the data in an #After method or if you intend to use this entity in multiple test cases then put the perist code into a #BeforeClass method.

Tapestry JPA Jackson Deserialization

I'm working on a project I didn't initially create, in which the data was stored in-memory. I'm curently moving this data into the database. I'm doing this using hibernate and tapestry JPA. At some point in the project Jackson Deserialization is used (actually in connection with a UI, but I doubt that's relevant), via the #JsonDeserialize annotation, with a deserializer class (let's call it DefinitionDeserializer). DefinitionDeserializer then creates an instance of a POJO representation (let's call it Definition) of a database table (D_DEFINITION). However, D_DEFINITION has a connection to another table (D_TYPE) (and hence another POJO (PeriodType)). To resolve this connection, I'm using a tapestry service (ConnectingService), which I usually inject via the #Inject annotation. However, I can't use this method of injection when the object (in which I'm trying to inject the service, i.e. DefinitionDeserializer) was created via the new keyword - which seems to be the case for the #JsonDeserialize annotation. I also can't use ConnectingService without injecting it via the #Inject keyword, because then I couldn't inject any other services in ConnectingService either, which I'm currently doing.
I'm hoping this description didn't confuse you too much, I can't share the actual code with you and I don't think a minimal example would be much better, as it's quite a complicated case and wouldn't be such a small piece of code. If you need one, however, I can try to provide one.
Basically what I need is a way to tell JsonDeserialize to take a tapestry service instead of creating an instance of DefinitionDeserializer itself.
Edit: The classes as examples:
public DefinitionDeserializer extends StdDeserializer<Definition> {
private static final long serialVersionUID = 1L;
//TODO: The injection doesn't work yet
#Inject
private ConnectingService connectingService;
public DefinitionDeserializer() {
this(null);
}
public DefinitionDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Definition deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Definition pd = new Definition();
JsonNode node = p.getCodec().readTree(p);
if (node.has("type"))
pd.setType(periodTypeDao.findByValue("PeriodType." + node.get("type").asText()));
return pd;
}
}
#Entity
#Table(name = Definition.TABLE_NAME)
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region =
JpaEntityModelConstants.CACHE_REGION_ADMINISTRATION)
public class Definition {
public static final String TABLE_NAME = "D_DEFINITION";
private static final long serialVersionUID = 389511526676381027L;
#Id
#SequenceGenerator(name = JpaEntityModelConstants.SEQUENCE_NAME, sequenceName = JpaEntityModelConstants.SEQUENCE_NAME, initialValue = 1, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = JpaEntityModelConstants.SEQUENCE_NAME)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "FK_TYPE", referencedColumnName = "ID")}
)
private PeriodType type;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public PeriodType getType() {
return type;
}
public void setType(PeriodType dpmType) {
this.type = dpmType;
}
//More columns
}
PeriodType looks pretty much the same as Definition.
//BaseService contains all the standard methods for tapestry JPA services
public interface ConnectingService extends BaseService<PeriodType> {
}
public class ConnectingServiceImpl extends BaseServiceImpl<PeriodType> implements ConnectingService {
public ConnectingServiceImpl() {
super (PeriodType.class);
}
}
Currently I'm using it like this (which doesn't work):
#JsonDeserialize(using = DefinitionDeserializer.class)
#JsonSerialize(using = DefinitionSerializer.class)
private Definition definition;
#JsonDeserialize doesn't create instances of deserialisers, it's just a hint for ObjectMapper to know which class to use when deserialising.
By default ObjectMapper uses Class.newInstance() for instantiating deserialisers, but you can specify custom HandlerInstantiator (ObjectMapper#setHandlerInstantiator()) in which you can use Tapestry's ObjectLocator to get instances of deserialisers, i.e. using ObjectLocator#autobuild(), or use ObjectLocator#getService() if your deserialisers are Tapestry services themselves.
Update:
public class MyHandlerInstantiator extends HandlerInstantiator
{
private final ObjectLocator objectLocator;
public MyHandlerInstantiator(ObjectLocator objectLocator)
{
this.objectLocator = objectLocator;
}
#Override
public JsonDeserializer<?> deserializerInstance(
DeserializationConfig config, Annotated annotated, Class<?> deserClass)
{
// If null returned here instance will be created via reflection
// you can always use objectLocator, or use it conditionally
// just for some classes
return objectLocator.autobuild(deserClass);
}
// Other method overrides can return null
}
then later when you're configuring ObjectMapper use #Injected instance of ObjectLocator to create an instance of custom HandlerInstantiator, i.e.:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setHandlerInstantiator(new MyHandlerInstantiator(objectLocator));
return objectMapper;

(CRUD) Repository for a large number of JPA classes

I can create a repository via defining an interface on the appropriate JPA class A like the following:
public interface ARepository extends CrudRepository<A, Long>
{
}
and I can use that in my Controller (for example) via
#Autowired
private ARepository aRepository;
and just can do things like this:
aRepository.save(..);
aRepository.findAll();
..
No problem so far.
But my problem is that I have ca. 500 JPA classes and need to access each table which means to define 500 Repositories in the style of above.
So does exist an thing to create that either dynamically via some Spring Data "magic" which from my point of view should exist otherwise the above would not be possible. It looks like this is similar to my problem.
Apart from that one more issue related to the above. I can define findBy... methods in the interface and in the background there will be generated a query method for this particular attribute. The question is also if this can be done in a dynamic way related to the previous question, cause I have groups of tables which need supplemental query methods..
There is spring-data-generator which can automatically generate the interfaces for you.
Regarding your 2nd question I don't think you that can be done in a dynamic way. Java is statically compiled and there's no way to add members dynamically. There could be a tool that generates code for those methods but if that tool generates methods for all combinations of columns you will end up with a huge amount of methods.
You can make a base abstract entity for your 500 classes an then create one repo for this class. (I think it's a common practice to have a BaseEntity class with id, version etc. for every entity in the project).
For simple repo methods (like save, findAll etc.) it will work right from the box (note - entities must have the equal id type). For example:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstarct class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
#Entity
public class Entity1 extends BaseEntity {
private String name;
}
#Entity
public class Entity2 extends BaseEntity {
private String name;
}
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
}
Note that BaseEntity must have #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) to prevent of using singe table base_entity for every entity. And their ids must not intersect (see #GeneratedValue(strategy = GenerationType.SEQUENCE)).
Usage:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BaseEntityRepoTest {
#Autowired private BaseEntityRepo repo;
#Before
public void setUp() throws Exception {
repo.save(asList(
new Entity1("entity1"),
new Entity2("entity2")
));
}
#Test
public void readingTest() throws Exception {
List<BaseEntity> entities = repo.findAll();
assertThat(entities).hasSize(2);
}
}
Related to your second question you can use this approach:
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
<T> T findById(Long id, Class<T> type);
}
Usage:
#Test
public void findById() {
final Entity1 entity1 = repo.findById(1L, Entity1.class);
final Entity2 entity2 = repo.findById(2L, Entity2.class);
assertThat(entity1).isNotNull();
assertThat(entity2).isNotNull();
}
But you can build repo query methods only for 'common' properties of inherited entities which are present in the base class. To make this method work you must move the name parameter to the BaseEntity:
<T> List<T> findAllByNameLike(String name, Class<T> type);

Spring Data JPA - insert a base class into the repository

I have a small application being a bridge between RabbitMQ and SQL database. It is meant to consume events (of few types) from the queue and store them in appropriate tables in the DB. In majority of cases, there is almost no processing between the Event and the Entity (just field copying). This is the reason why I have injected a Dozer mapper that makes the conversion - it works flawlessly. The difficult part is saving a generic object to the repository without having to use switch + instanceof as a last resort.
Code:
#Service
public class EventProcessorImpl implements EventProcessor {
#Autowired
private Mapper mapper; // a Dozer mapper instance
#Override
public void process(final BaseEvent event) {
final BaseEntity entity = mapper.map(event, BaseEntity.class);
// TODO save the entity to the database after it's transformed
}
}
The BaseEntity is a base class for entites, as follows:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
#Id
private String guid;
public String getGuid() {
return guid;
}
public void setGuid(final String guid) {
this.guid = guid;
}
}
#NoRepositoryBean
public interface BaseRepository<T extends BaseEntity>
extends CrudRepository<T, String> {
}
#Repository
public interface EmailOpenedRepository
extends BaseRepository<EmailOpened> {
}
The question is - how to save the entity, given that:
it is passed as a base class (what I consider as an advantage, but this can be changed)
there are quite a couple of event types (now 5, but can explode to 30 in a few months)
What I have tried:
I have #Autowired an instance of BaseRepository and tried calling repository.save(entity), but it fails on app startup due to multiple bean definitions available.
Based on the question, I have successfully implemented the following, but I don't know whether this is a correct approach:
public void process(final BaseEvent event) {
final BaseEntity entity = mapper.map(event, BaseEntity.class);
final CrudRepository repository = (CrudRepository) new Repositories(context)
.getRepositoryFor(entity.getClass());
repository.save(entity);
}
I thought of iterating over all available beans of BaseRepository and finding the one that will support this type (findFirst(repository -> repository.supports(entity.getType())), but Spring Data JPA repositories are interfaces and I cannot store the supported type in the interface.

Categories

Resources