How to test a jpa readonly query? - spring-data-jpa

The model is simplified for the question.
I have this entity:
#Entity
public class Formation {
#Id
Long id;
String login;
String code;
String level;
// geters and Setters
With this repository:
public interface FormationRepository extends JpaRepository<Formation, Long> {
#Query(value = "SELECT UNIQUE l.id, l.login, d.code,d.level\n"
" FROM table_login l,\n" +
" table_diploma d,\n" +
" WHERE
" l.fhab_key = d.fhab_key\n" +
" AND l.login= :login", nativeQuery = true)
List<Formation> findAllByLogin(#Param("login")String login);
So far so good, this works.
Now I want to add test for the repository (with and h2database). But I can't save data, as the entity isn't mapped to a single table.
So this won't work:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class FormationRepositoryTest {
#Autowired
FormationRepository formationRepository;
#Test
public void communeRepositoryTest() {
Formation formation = new Formation();
formation.setId(123L);
formation.setDlog_login("123");
formationRepository.save(formation); // ok
formationRepository.findAllByLogin("123"); // ko -> Caused by: Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: scolarite.scol_droit_login is not mapped
}
}
Here I need to add, that I don't want sql files in my app.
So what would be a solution here ?
Change my model to create an entity by table ? (in real, my request use 8 inner joins, so it will be quite long to code all that...)
Another solution ?

If you do not want to create the entities for all your tables, you could pre-populate your testing H2 database by defining schema.sql and data.sql on your classpath.
schema.sql will contain the DDL statements to create all tables involved (you mention 8 tables).
data.sql will contain the insert statements needed to make your custom sql return login = 123.
You can find an example here.
Then your test code would be like:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class FormationRepositoryTest {
#Autowired
FormationRepository formationRepository;
#Test
public void communeRepositoryTest() {
List<Formation> formations = formationRepository.findAllByLogin("123");
Assert.assertEquals(formations.size(), 1);
}
}

Related

JPA - perform an insert on a Postgres table whose primary key is generated from a database trigger

I am writing an API where I am inserting a record into a table (Postgres). I was hoping to use JPA for the work. Here is the potential challenge: the primary key for the insert is generated from a database trigger, rather than from sequence count or similar. In fact, the trigger creates the primary key using the values of other fields being passed in as part of the insert. So for example,
if I have a entity class like the following:
#Entity
#Validated
#Table(name = "my_table", schema="common")
public class MyModel {
#Id
#Column(name = "col_id")
private String id;
#Column(name = "second_col")
private String secCol;
#Column(name = "third_col")
private String thirdCol;
public MyModel() {
}
public MyModel(String id, String secCol, String thirdCol) {
this.id = id;
this.secCol = secCol;
this.thirdCol = thirdCol;
}
}
I would need the col_id field to somehow honor that the key is generated from the trigger, and the trigger would need to be able to read the values for second_col and third_col in order to generate the primary key. Finally, I would need the call to return the value of the primary key.
Can this be done with jpa and repository interface such as:
public interface MyRepo extends JpaRepository <MyModel, String> {
}
and then use either default save method such as myRepo.saveAndFlush(myModel) or custom save methods? I can't find anything on using JPA with DB triggers that generating keys. If it cannot be done with JPA, I would be grateful for any alternative ideas. Thanks.
ok, I was able to get this to work. It required writing a custom query that ignored the primary key field:
public interface MyRepo extends JpaRepository <MyModel, String> {
#Transactional
#Modifying
#Query(value = "INSERT INTO my_table(second_col, third_col)", nativeQuery = true)
int insertMyTable(#Param("second_col") String second_col, #Param("third_col") String third_col);
}
The model class is unchanged from above. Because it was executed as a native query, it allowed postGres to do its thing uninterrupted.

JPA Query method not returning anything

I currently have it working with a custom n1ql query, however it's such a simple query, I figured I could just use the built in jpa query method, however I can't figure out the key words, because I'm not getting anything back.
This code works:
#Query("SELECT meta().id as _ID, meta().cas as _CAS, * FROM `my-bucket` mb " +
"WHERE mb.name like $1 OR ANY Parent " +
"IN mb.Parents SATISFIES Parent.name like $1 END")
List<MyObject> searchObjectByName(String name);
This however doesn't work
#N1qlPrimaryIndexed
public interface MyObjectRepository extends CouchbasePagingAndSortingRepository<MyObject, String> {
List<MyObject> findBySecondObjectNameContains(String name);
}
#Data
#Document
public class MyObject{
#Id
private String objectId;
#Field
private SecondObject secondObject;
}
#Data
public class SecondObject {
#Field
private String name;
}
My test method:
#Autowired
private MyObjectRepository myObjectRepository;
#Test
public void testFind() {
List<MyObject> myObjects = myObjectRepository.findBySecondObjectNameContains("my name");
Assert.assertNotNull(myObjects);
}
The query looks correct, a few things that might be missing:
1) In your test, are you sure that "my name" isn't supposed to be "my name%"?
2) Check if you have a primary or secondary index that covers this query (run the same query via web console)
3) When did you insert the data? If you haven't configured couchbase to be strong consistent, you might have been reading an old version of your data

Why is the same database entry represented by multiple JPA bean instances?

Today I stumbled over some unexpected behaviour of EclipseLink. (I don't know if this is bound to EclipseLink or if this is the same for all JPA providers.)
I assumed that retrievals of a managed JPA bean always return references to the same object instance when issued inside the same transaction (using the same EntityManager).
If that is right, I don't know why I receive an error when I execute the following test case:
#Test
public void test_1() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual =
em.createQuery("SELECT x from Product x where x.id = "
+ prod.getId(), Product.class).getSingleResult();
// Then:
assertThat(actual).isSameAs(prod); // <-- FAILS
em.getTransaction().commit();
}
The statement marked with "FAILS" throws the following AssertionError:
java.lang.AssertionError:
Expecting:
<demo.Product#35dece42>
and actual:
<demo.Product#385dfb63>
to refer to the same object
Interestingly the following slightly modified test succeeds:
#Test
public void test_2() {
EntityManager em = newEntityManager();
em.getTransaction().begin();
// Given:
Product prod = newProduct();
// When:
em.persist(prod);
em.flush();
Product actual = em.find(Product.class, prod.getId());
// Then:
assertThat(actual).isSameAs(prod); // <-- SUCCEEDS
em.getTransaction().commit();
}
Obviously there is a difference between finding and querying objects.
Is that the expected behaviour? And why?
--Edit--
I think I found the source of the problem: Product has an ID of type ProductId.
Here is the relevant code:
#Entity
#Table(name = "PRODUCT")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID", nullable = false)
#Converter(name = "productIdConverter", converterClass = ProductIdConverter.class)
#Convert("productIdConverter")
private ProductId id;
#Column(name = "NAME", nullable = false)
private String name;
[...]
}
The #Convert and #Converter annotations are EclipseLink-specific.
Unlike JPA 2.1 Converters you may place them on ID fields.
But it seems that in certain circumstances EclipseLink has problems to find a managed bean in its session cache if that bean uses a custom type for its ID field.
I guess I have to file a bug for that.
I found the cause of the problem and a solution.
We are using a custom ID class (ProductId) for Product, together with a custom (EclipseLink-specific) Converter-Class ProductIdConverter which has a bad implementation of the convertObjectValueToDataValue(...) method.
Here is the relevant code:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return longValue;
}
Please note that the method returns Long instances (or null).
But since we are using Oracle as our database backend and have declared the product's ID column as NUMBER, the JDBC Driver maps the column value as BigDecimal. This means, we have to make sure, that our convertObjectValueToDataValue(...) also returns BigDecimal instances.
So the correct implementation is:
/**
* Convert the object's representation of the value to the databases' data representation.
*/
#Override
public final Object convertObjectValueToDataValue(Object objectValue, Session session) {
if (objectValue == null) {
return null;
}
Long longValue = ((ProductId) objectValue).getLong();
return BigDecimal.valueOf(longValue);
}
Now this method returns only BigDecimal instances.

Spring Data JPA And NamedEntityGraphs

currently I am wrestling with being able to fetch only the data I need. The findAll() method needs to fetch data dependant on where its getting called.
I do not want to end up writing different methods for each entity graph.
Also, I would avoid calling entitymanagers and forming the (repetitive) queries myself.
Basicly I want to use the build in findAll method, but with the entity graph of my liking. Any chance?
#Entity
#Table(name="complaints")
#NamedEntityGraphs({
#NamedEntityGraph(name="allJoinsButMessages", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
}),
#NamedEntityGraph(name="allJoins", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre"),
#NamedAttributeNode("complaintMessages")
}),
#NamedEntityGraph(name="noJoins", attributeNodes = {
})
})
public class Complaint implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private long id;
private Timestamp date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer")
private User customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "handling_employee")
private User handling_employee;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="genre")
private Genre genre;
private boolean closed;
#OneToMany(mappedBy = "complaint", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ComplaintMessage> complaintMessages = new ArrayList<ComplaintMessage>();
//getters and setters
}
And my JPARepository
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
}
We ran into a similar problem and devised several prospective solutions but there doesn't seem to be an elegant solution for what seems to be a common problem.
1) Prefixes. Data jpa affords several prefixes (find, get, ...) for a method name. One possibility is to use different prefixes with different named graphs. This is the least work but hides the meaning of the method from the developer and has a great deal of potential to cause some non-obvious problems with the wrong entities loading.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#EntityGraph(value = "User.membershipYears", type = EntityGraphType.LOAD)
User readByUserId(int id);
}
2) CustomRepository. Another possible solutions is to create custom query methods and inject the EntityManager. This solution gives you the cleanest interface to your repository because you can name your methods something meaningful, but it is a significant amount of complexity to add to your code to provide the solution AND you are manually grabbing the entity manager instead of using Spring magic.
interface UserRepositoryCustom {
public User findUserWithMembershipYearsById(int id);
}
class UserRepositoryImpl implements UserRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public User findUserWithMembershipYearsById(int id) {
User result = null;
List<User> users = em.createQuery("SELECT u FROM users AS u WHERE u.id = :id", User.class)
.setParameter("id", id)
.setHint("javax.persistence.fetchgraph", em.getEntityGraph("User.membershipYears"))
.getResultList();
if(users.size() >= 0) {
result = users.get(0);
}
return result;
}
}
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
}
3) JPQL. Essentially this is just giving up on named entity graphs and using JPQL to handle your joins for you. Non-ideal in my opinion.
#Repository
#Transactional
public interface UserRepository extends CrudRepository<User, Integer>, UserRepositoryCustom {
#EntityGraph(value = "User.membershipYearsAndPreferences", type = EntityGraphType.LOAD)
User findByUserID(int id);
#Query("SELECT u FROM users WHERE u.id=:id JOIN??????????????????????????")
User findUserWithTags(#Param("id") final int id);
}
We went with option 1 because it is the simplest in implementation but this does mean when we use our repositories we have have to look at the fetch methods to make sure we are using the one with the correct entity graph. Good luck.
Sources:
JPA EntityGraph with different views using Spring
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
I don't have enough reputation to post all of my sources. Sorry :(
We had the same issue and built a Spring Data JPA extension to solve it :
https://github.com/Cosium/spring-data-jpa-entity-graph
This extension allows to pass named or dynamically built EntityGraph as an argument of any repository method.
With this extension, you would have this method immediatly available:
List<Complaint> findAll(Sort sort, EntityGraph entityGraph);
And be able to call it with an EntityGraph selected at runtime.
Use #EntityGraph together with #Query
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
#EntityGraph(value = "allJoinsButMessages" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoinsButMessages();
#EntityGraph(value = "allJoins" , type=EntityGraphType.FETCH)
#Query("SELECT c FROM Complaint ORDER BY ..")
#Override
List<Complaint> findAllJoin();
...
}
Using the #EntityGraph annotation on a derived query is possible, as I found out from This article. The article has the example:
#Repository
public interface ArticleRepository extends JpaRepository<Article,Long> {
#EntityGraph(attributePaths = "topics")
Article findOneWithTopicsById(Long id);
}
But I don't think there's anything special about "with" and you can actually have anything between find and By. I tried these and they work (this code is Kotlin, but the idea is the same):
interface UserRepository : PagingAndSortingRepository<UserModel, Long> {
#EntityGraph(attributePaths = arrayOf("address"))
fun findAnythingGoesHereById(id: Long): Optional<UserModel>
#EntityGraph(attributePaths = arrayOf("address"))
fun findAllAnythingGoesHereBy(pageable: Pageable): Page<UserModel>
}
The article had mentioned the caveat that you can't create a method similar to findAll which will query all records without having a By condition and uses findAllWithTopicsByIdNotNull() as an example. I found that just including By by itself at the end of the name was sufficient: findAllWithTopicsBy(). A little more terse but maybe a little more confusing to read. Using method names which end with just By without any condition may be in danger of breaking in future versions in Spring since it doesn't seem like an intended use of derived queries name.
It looks like the code for parsing derived query names in Spring is here on github. You can look there in case you're curious about what's possible for derived queries repository method names.
These are the spring docs for derived queries.
This was tested with spring-data-commons-2.2.3.RELEASE
EDIT: this doesn't actually work. Ended up having to go with https://github.com/Cosium/spring-data-jpa-entity-graph. The default method LOOKS correct, but doesn't successfully override the annotations.
Using JPA, what I found works is to use a default method, with a different EntityGraph annotation:
#Repository
public interface ComplaintRepository extends JpaRepository<Complaint, Long>{
List<Complaint> findByClosed(boolean closed);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee" }, type=EntityGraphType.FETCH)
#Override
List<Complaint> findAll(Sort sort);
#EntityGraph(attributePaths = {"customer", "genre", "handling_employee", "messages" }, type=EntityGraphType.FETCH)
default List<Complaint> queryAll(Sort sort){
return findAll(sort);
}
}
You don't have to do any of the re-implementation, and can customize the entity graph using the existing interface.
Can you try create EntiyGraph name with child that you will request and give same name to the find all method.
Ex:
#EntityGraph(value = "fetch.Profile.Address.record", type = EntityGraphType.LOAD)
Employee getProfileAddressRecordById(long id);
For your case:
#NamedEntityGraph(name="all.Customer.handling_employee.genre", attributeNodes = {
#NamedAttributeNode("customer"),
#NamedAttributeNode("handling_employee"),
#NamedAttributeNode("genre")
})
method name in repository
#EntityGraph(value = "all.Customer.handling_employee.genre" , type=EntityGraphType.FETCH)
findAllCustomerHandlingEmployeeGenre
This way you can keep track of different findAll methods.

Assigning Sequences for all JPA entities using SessionCustomizer

I am trying to use a SessionCustomizer to automatically generate Sequences in EclipseLink which already exist in the database following a special naming convention. For example an entity called Item is mapped to a table called ITEMS which has a four letter alias ITEM and a database sequence called ITEM_ID_SEQ for unique ID generation.
I am using an annotation as a marker to hold the alias name on the entity class because we are using it for other purposes, too:
package jpa.namingsupport;
// imports omitted
#Target(TYPE)
#Retention(RUNTIME)
public #interface Alias {
String name();
}
Entities look like this:
package jpa.entities;
// imports omitted
#Entity
#Table(name = "ITEMS")
#Alias(name = "ITEM")
public class Item {
#Id
private Long id;
#Version
private Long version;
private String name;
// setters and getters omitted
}
Using a SessionCustomizer registered correctly and verified running on startup to create and add the Sequences to the entities:
package jpa.namingsupport;
// imports omitted
public class AliasCustomizer implements SessionCustomizer {
#Override
public void customize(Session session) throws Exception {
Map<Class, ClassDescriptor> entities = session.getDescriptors();
for (Class entity : entities.keySet()) {
customizeSequence(aliasNameFor(entity), entities.get(entity), session);
}
}
private String aliasNameFor(Class entity) {
Alias alias = (Alias) entity.getAnnotation(Alias.class);
return alias.name();
}
private void customizeSequence(String alias, ClassDescriptor descriptor, Session session) {
NativeSequence sequence = new NativeSequence(underscores(alias, "ID", "SEQ"), 1);
session.getLogin().addSequence(sequence);
descriptor.setSequenceNumberName(sequence.getName());
descriptor.setSequenceNumberField(descriptor.getPrimaryKeyFields().get(0));
descriptor.setSequence(sequence);
}
private String underscores(String... parts) {
return StringUtils.arrayToDelimitedString(parts, "_");
}
}
But when I am running my tests the ID is not assigned from the Sequence before saving:
[EL Warning]: 2013-07-14 20:32:32.571--UnitOfWork(1908148255)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException: NULL nicht zulässig für Feld "ITEM_ID"
NULL not allowed for column "ITEM_ID"; SQL statement:
INSERT INTO ITEMS (ITEM_NAME, ITEM_VERSION) VALUES (?, ?) [23502-172]
Any hints and ideas what I am missing in my code? What I am seeing is that there is no reference to the ITEM_ID column in the generated insert statement.
Why don't you just put #GeneratedValue(strategy=SEQUENCE, generator="ITME_ID_SEQ") on your id?
For your customizer, don't call descriptor.setSequence(), this should be done be initializaiton.
The SQL is expecting the id to being using an IDENTITY value, you need to configure your table for this. If you want to use SEQUENCE instead, then pass false into new NativeSequence(name, increment, false). H2 supports both IDENTITY and SEQUENCE, and NativeSequence defaults to using IDENTITY, false means SEQUENCE.