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.
Related
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.
I'm writing some hql queries using the #Query annotation in a spring data jpa repository. I know that I can use the methods from the repository interface, but for learning purpose, I'm writing them explicitly.
Here is my Main class
#SpringBootApplication
public class Main implements CommandLineRunner {
#Autowired
PersonRepository personRepository;
public static void main( String[] args ) {
SpringApplication.run(Main.class, args);
}
/**
* if we delete the transactional annotation-> we get an exception
*/
#Override
#Transactional
public void run( String... args ) throws Exception {
saveOperation();
deleteOperationUsingHql();
}
private void saveOperation() {
Person p = new Person("jean", LocalDate.of(1977,12,12));
personRepository.save(p);
}
private void deleteOperationUsingHql() {
personRepository.deleteUsingHql(1L);
personRepository.flush();
Optional<Person> p = personRepository.findById(1L);
if (p.isPresent()){
System.out.println("still present");
}
}
}
My personRepository interface
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query(value = "select p from Person p where p.id=?1")
List<Person> getById( Long id);
#Modifying
#Query(value = "delete from Person p where p.id=:id")
void deleteUsingHql( Long id );
}
The person class
#Entity
#Table(name = "Person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate date;
// constructors,getters...omitted for brievety
}
everything is running well, but for the deleteOperationUsingHql(), even If I deleted the person from the database and even if I flush the modification to the database, the person with id=1 is still returned by the findById(1L) method. What should I do for making the findById(1L) returning an empty Optional.
My second question is about the #Transactional annotation, I know how it works in details, but I don't know why if we delete it, We get the following exception
Caused by: javax.persistence.TransactionRequiredException: Executing
an update/delete query
Could someone explains why I'm getting this exception when #Transactional is removed.
even If I deleted the person from the database and even if I flush the modification to the database, the person with id=1 is still returned by the findById(1L) method
That's normal, because you use a query to delete the person, instead of actually using the repository (and thus the EntityManager) delete method. Queries bypass the session cache completely, so Hibernate has no idea that this person has been deleted, and returns the instance in its cache. Solution: don't use a query. Alternate solution, clear the cache after deleting (for example by setting the clearAutomaticallyflag of the Modifying annotation to true).
Could someone explains why I'm getting this exception when #Transactional is removed.
Because when #Transactional is removed, there is no transaction being started by SPring before executing the method, and as you can see from the error message, delete queries must be executed inside a transaction.
I have Java EE application with Hibernate. I want to implement a feature that every minute updates one of existing rows in database. I have following classes:
#Singleton
#Startup
public class TimerRunnerImpl implements TimerRunner {
#EJB
private WorkProcessor workProcessor;
private String jobId;
#Timeout
#AccessTimeout(value = 90, unit = TimeUnit.MINUTES)
#TransactionAttribute(value = TransactionAttributeType.NEVER)
public void doProcessing(Timer timer) {
jobId = workProcessor.doWork(jobId);
}
//other methods: startTimer, etc
}
#Stateless
public class WorkProcessorImpl implements WorkProcessor {
#EJB
private MyEntityDao myEntityDao;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
#Override
public String doWork(String jobId) {
if (jobId == null) {
MyEntity myEntity = myEntityDao.oldestEntityToProcess();
String uuid = UUID.randomUUID().toString();
myEntity.setJobId(uuid);
myEntityDao.update(myEntity); // this invokes merge()
return uuid;
} else {
// line below can never find entity, although there is one in DB
MyEntity myEntity = myEntityDao.findByJobId(jobId);
myEntity.setSomeProperty("someValue");
// some other updates
myEntityDao.update(myEntity); // this invokes merge()
return jobId;
}
}
}
First run of doWork updates MyEntity with job ID. This is being persisted into database - I can query it manually from SQLDeveloper. Second run always fails to find entity by job ID. In case I try to retrieve it by entity_id in debug mode, the object retrieved from Entity Manager has job id with previous value.
This is not cache problem, I have tried on each run to evict all cache at the beginning and results are identical.
As far as I understand, transaction is around workProcessor.doWork(jobId). I find confirmation of this by the fact that when this method returns I can see changes in DB. But why does EntityManager keeps my unmodified object and returns it when I query for it?
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.
Hi I have Hibernate project with QUeryDSL 3.6.0, when I have in my service class only findAll() methods, everyThing was OK. But whe I add findByID an error appears.
public class ArticleServiceImpl extends ArticleService {
QArticle article = QArticle.article;
#Override
public List<Article> findAll() {
return query.from(article).fetchAll().list(article);
}
public Article findById(#Nonnull final long id) {
return query.from(article).where(article.id.eq(id)).uniqueResult(article);
}
}
An the error is:
Exception in thread "main" java.lang.IllegalStateException: article is already used
at com.mysema.query.DefaultQueryMetadata.addJoin(DefaultQueryMetadata.java:160)
at com.mysema.query.support.QueryMixin.from(QueryMixin.java:189)
at com.mysema.query.jpa.JPAQueryBase.from(JPAQueryBase.java:88)
at com.mysema.query.jpa.JPAQueryBase.from(JPAQueryBase.java:32)
at com.example.hibernate.services.ArticleServiceImpl.findById(ArticleServiceImpl.java:30)
at com.example.hibernate.core.Main.main(Main.java:42)
What happens? I saw that queries are not tread-safe. But how to use Q-class in the two different methods?
Edit:
protected JPQLQuery query = new JPAQuery(entityManager);
it is protected variable that comes from ArticleService.
This exception is thrown whenever there is a repeatable call to the same generated QEntity in from() clause for the same instance of JPAQuery().
Here is an example (DISCLAIMER: This is going to be very, very dumb example just to illustrate the problem).
Let's say we have an entity called MyEntity, and we try to get two MyEntity's from a database in a way, that the first result will be for a given id, and the second result will be the one which has id+1
public List<MyEntity> findMyDumbEntities(long id) {
QMyEntity qMyEntity = QMyEntity.myEntity;
JPAQuery jpaQuery = new JPAQuery(entityManager);
MyEntity myFirstEntity = jpaQuery.from(qMyEntity).where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
MyEntity mySecondEntity = jpaQuery.from(qMyEntity).where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
return Arrays.asList(myFirstEntity, mySecondEntity);
}
And when trying to call this method we will see the following exception:
java.lang.IllegalStateException: qMyEntity is already used
Why? Because we have one instance of JPAQuery and we are repeating call to the same entity (we have two jpqQuery.from(qMyEntity)). To solve the problem we just need to get JPAQuery instance each time we want to query something, so we need to change our code to
public List<MyEntity> findMyDumbEntities(long id) {
QMyEntity qMyEntity = QMyEntity.myEntity;
MyEntity myFirstEntity = new JPAQuery(entityManager).from(qMyEntity).where(qMyEntity.id.eq(id)).uniqueResult(qMyEntity);
MyEntity mySecondEntity = new JPAQuery(entityManager).from(qMyEntity).where(qMyEntity.id.eq(id+1)).uniqueResult(qMyEntity);
return Arrays.asList(myFirstEntity, mySecondEntity);
}
So to fix your problem, instead of having JPQQuery initialized once
protected JPQLQuery query = new JPAQuery(entityManager);
Change that you get each time new JPAQuery, for example
protected JPQLQuery jpaQuery() {
return new JPAQuery(entityManager);
}
And then in your service implementation
#Override
public List<Article> findAll() {
return jpaQuery().from(article).fetchAll().list(article);
}
public Article findById(#Nonnull final long id) {
return jpaQuery().from(article).where(article.id.eq(id)).uniqueResult(article);
}