I am working on a project Spring and Java, generated using JHipster.
I encounter a problem with the following items of Spring: Specification, Criteria, and filtering a list of items.
I have a functioning SQL query, than I want to "translate" as a Spring Specification. But, I don't find how to do it, as the query works on 3 tables, and has 2 conditions.
Basically, the work concerns 3 tables: contract, transfer & entry. A contract can be inside one or several transfers, and a transfer contains 1 to several contracts. An entry is the link between these two tables (an entry contains a contract_id and a transfer_id).
The needs is to use the specification to get a list of contracts which are linked to a not received transfer.
How can i write this?
I have already looked at several stackoverflow posts and questions, but I found answers for a join between only two tables, or how to write specifications on an entity.
Here, the query I want to translate:
SELECT c.*
FROM contract AS c
LEFT JOIN entry AS e ON e.contract_id = c.id
INNER JOIN transfer AS t ON t.id = e.transfer_id
AND t.status != 'RECEIVED'
Here, an example of the existing Contract Specification created by JHipster Here you can see how the JHipster specification are used as filter.
I want to add the new specification inside the already existing ones
private Specification<Contract> createSpecification(ContractCriteria criteria) {
Specification<Contract> specification = Specification.where(null);
if (criteria == null) {
return specification;
}
return specification.and(buildStringSpecification(criteria.getContractNumber(), Contract_.contractNumber))
.and(buildSpecification(criteria.getStatus(), Contract_.status))
.and(buildSpecification(
criteria.getStoreCode(),
root -> root.join(Contract_.store, JoinType.LEFT).get(Store_.code)));
Okay, so I think I understood more or less how your entities are designed. I created a quick project using the following JDL:
entity Contract {contractNumber String, status String}
entity Transfer {status String}
entity Entry {}
relationship OneToMany {
Transfer{entries} to Entry{transfer},
Contract{entries} to Entry{contract}
}
service all with serviceClass
filter all
This is not how I would have designed the entities, but this is how you have them on your project and also as succinct as I could manage.
After importing this JDL in a fresh jhipster project your requirement is to filter contracts by transfer status.
The first thing we need to do is create a new StringFilter in your ContractCriteria.java (my status is just a String for simplicity, if yours is an Enum then you need you create the corresponding enum filter).
ContractCriteria.java
public class ContractCriteria implements Serializable, Criteria {
// ...
private StringFilter transferStatus;
public ContractCriteria(ContractCriteria other){
// ...
this.transferStatus = other.transferStatus == null ? null : other.transferStatus.copy();
}
// ...
public StringFilter getTransferStatus() {
return transferStatus;
}
public void setTransferStatus(StringFilter transferStatus) {
this.transferStatus = transferStatus;
}
// ...
Remember to add your new filter to the hashCode() and equals() too. Once the new filter is implemented you just have to use it in your query service.
ContractQueryService.java
protected Specification<Contract> createSpecification(ContractCriteria criteria) {
Specification<Contract> specification = Specification.where(null);
if (criteria != null) {
// ...
if (criteria.getTransferStatus() != null) {
specification = specification.and(buildSpecification(criteria.getTransferStatus(),
root -> root.join(Contract_.entries, JoinType.LEFT)
.join(Entry_.transfer, JoinType.INNER)
.get(Transfer_.status)));
}
}
return specification;
}
The three relevant entities are as follow:
Contract has a property: Set<Entry> entries
Entry has properties: Transfer transfer and Contract contract
Transfer has a property: Set<Entry> entries
For quick development, jhipster comes with Swagger so you can test all your APIs live (/admin/docs with admin privileges). I leave the client side to you :)
Related
public List<Post> getPosts(String city) {
// if City parameter is presented(When searched by an user)
if (!Strings.isNullOrEmpty(city)) {
return postRepository.findAllByCityOrderByIdDesc(city).stream().map(obj -> {
obj.getUser().setPassword("");
return obj;
}).collect((Collectors.toList()));
} else {
return postRepository.findAllByOrderByIdDesc().stream().map(obj -> {
obj.getUser().setPassword("");
return obj;
}).collect((Collectors.toList()));
}
}
I tried to change object values with the stream map function after fetching data from the DB, and the values in the DB changed too.
are they two connected?
The fact that you're using a stream to operate on the values in the list is entirely irrelevant:
JPA is a "magic" technology that specifically provides the feature of automatically persisting changes that you make to JPA entity objects.
The stream() just takes an existing collection of objects and operates on them.
You are modifying the objects in map, which is not recommended in general. You could detach the users from the EntityManager, but...
you should be mapping these to DTOs rather than presenting the entity objects to your top-level API layer, and
when you have chunks like this that need to be isolated, you should consider separating your database model so that the "public profile" part of the user and the authentication part aren't grouped together.
I'm refactoring a code base to get rid of SQL statements and primitive access and modernize with Spring Data JPA (backed by hibernate). I do use QueryDSL in the project for other uses.
I have a scenario where the user can "mass update" a ton of records, and select some values that they want to update. In the old way, the code manually built the update statement with an IN statement for the where for the PK (which items to update), and also manually built the SET clauses (where the options in SET clauses can vary depending on what the user wants to update).
In looking at QueryDSL documentation, it shows that it supports what I want to do. http://www.querydsl.com/static/querydsl/4.1.2/reference/html_single/#d0e399
I tried looking for a way to do this with Spring Data JPA, and haven't had any luck. Is there a repostitory interface I'm missing, or another library that is required....or would I need to autowire a queryFactory into a custom repository implementation and very literally implement the code in the QueryDSL example?
You can either write a custom method or use #Query annotation.
For custom method;
public interface RecordRepository extends RecordRepositoryCustom,
CrudRepository<Record, Long>
{
}
public interface RecordRepositoryCustom {
// Custom method
void massUpdateRecords(long... ids);
}
public class RecordRepositoryImpl implements RecordRepositoryCustom {
#Override
public void massUpdateRecords(long... ids) {
//implement using em or querydsl
}
}
For #Query annotation;
public interface RecordRepository extends CrudRepository<Record, Long>
{
#Query("update records set someColumn=someValue where id in :ids")
void massUpdateRecords(#Param("ids") long... ids);
}
There is also #NamedQuery option if you want your model class to be reusable with custom methods;
#Entity
#NamedQuery(name = "Record.massUpdateRecords", query = "update records set someColumn=someValue where id in :ids")
#Table(name = "records")
public class Record {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//rest of the entity...
}
public interface RecordRepository extends CrudRepository<Record, Long>
{
//this will use the namedquery
void massUpdateRecords(#Param("ids") long... ids);
}
Check repositories.custom-implementations, jpa.query-methods.at-query and jpa.query-methods.named-queries at spring data reference document for more info.
This question is quite interesting for me because I was solving this very problem in my current project with the same technology stack mentioned in your question. Particularly we were interested in the second part of your question:
where the options in SET clauses can vary depending on what the user
wants to update
I do understand this is the answer you probably do not want to get but we did not find anything out there :( Spring data is quite cumbersome for update operations especially when it comes to their flexibility.
After I saw your question I tried to look up something new for spring and QueryDSL integration (you know, maybe something was released during past months) but nothing was released.
The only thing that brought me quite close is .flush in entity manager meaning you could follow the following scenario:
Get ids of entities you want to update
Retrieve all entities by these ids (first actual query to db)
Modify them in any way you want
Call entityManager.flush resulting N separate updates to database.
This approach results N+1 actual queries to database where N = number of ids needed to be updated. Moreover you are moving the data back and forth which is actually not good too.
I would advise to
autowire a queryFactory into a custom repository
implementation
Also, have a look into spring data and querydsl example. However you will find only lookup examples.
Hope my pessimistic answer helps :)
In older versions SDN we had following interface for repositories
org.springframework.data.neo4j.repository.RelationshipOperationsRepository;
public interface UserRelationRepository extends GraphRepository<UserEntity>, RelationshipOperationsRepository<UserEntity> {
MakeFriend rel = userRepository.getRelationshipBetween(startUser, endUser, MakeFriend.class, RelTypes.FRIEND.name());
if (rel != null) {
startUser.getFirstname() + " + " + endUser.getFirstname());
}
userRepository.createRelationshipBetween(startUser, endUser, MakeFriend.class, RelTypes.FRIEND.name());
userRepository.createRelationshipBetween(endUser, startUser, MakeFriend.class, RelTypes.FRIEND.name());
But current version does not support it. Which is the best way implement functionality like createRelationshipBetween or getRelationshipBetween in SDN?
SDN 4 does not support managing low-level graph operations using APIs.
Instead the graph operations to be performed are inferred from your domain model classes and what you do with them.
For example, create a User class as follows:
class User {
List<User> friends = new ArrayList();
}
If you now adding or remove Users in the friends list and save the User in the normal way via the standard Repository methods, this will achieve what you need automatically - the appropriate relationships will be added/removed. You don't have to tell SDN what to do because the point of an ORM/OGM is to hide you from the underlying data model and its implementation details and allow you to manipulate the domain model itself.
If you really need to perform these low-level operations directly on the graph, you use Cypher with a query method.
You can find out more about SDN 4.1 here
As part of my program, I'm using relational tables which hold information such as - user role, job category etc. Each table may have slightly differing fields - for example:
User Role Table has the following fields:
id (auto-generated)
role (eg Planner, Admin etc)
role_description (description of above role)
enabled (toggle this role on/off)
Job Category Table:
id (auto-generated)
category (eg Service, Maintenance etc)
category_description (description of above)
category_group (categories are grouped into management areas)
...
enabled (toggle category on/off)
The lists can be changed by the end user so I need to provide an admin section to enable new roles/categories to be added.
I had thought of creating a routine where I pass the entity class of the role/category etc and have it generate an array which can be used to populate the admin section but have only been able to do this for the 1st two columns - eg id/role or id/category.
With the fields differing between each entity, is there a way that I can do this? Or will I have to create a method in each of the entities - such as getRoleList and getCategoryList etc?
Thanks.
After a bit of experimenting, I've decided to implement this in the following way.
I've added methods to my database helper class that will read the list and populate an array. I'll have to create a separate method for each entity but I've decided this would be necessary due to the differences between the classes.
I'm not 100% sure that this is the most efficient way of accomplishing this but it does what I need (for now).
One of the methods:
public static UserRole[] getUserRoleList(String order, Boolean reverseOrder) throws SQLException {
Session session = openSession();
List<UserRole> list;
if (!reverseOrder) {
// obtain list and sort by provided field in ascending order
list = session.createCriteria(UserRole.class).addOrder(Order.asc(order)).list();
} else {
// sort descending
list = session.createCriteria(UserRole.class).addOrder(Order.desc(order)).list();
}
// return UserRole[]
return list.toArray((UserRole[]) Array.newInstance(UserRole.class, list.size()));
}
The rest of the methods will be pretty much identical (substituting the entity/class names). The only difference would be adding another argument for some entities (enabled Boolean, so I can return only items in the list which are enabled).
Edit:
Since posting the above, I changed my mind and moved to a generic method to obtain lists, passing in the entity class as below:
public static List getList(Class entity, String order, Boolean reverseOrder, Boolean enabled) {
// stripped for brevity...
list = session.createCriteria(entity)
.add(Restrictions.eq("enabled", true))
.addOrder(Order.asc(order)).list();
// stripped more...
return list;
}
Casting when calling the method:
List<User> userList = DatabaseHelper.getList(User.class);
I am using Spring JDBC and I am a bit unsure on how to work with multiple one-to-many relations (or many-to-many). In this case I am injecting a repository into one of my resultsetextractors so that I can retrieve its associations. Is this the way to do it? Is it bad? Are there other better ways?
Note: I have left out the injection of repository
public class SomeResultSetExtractor implements ResultSetExtractor {
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
List result = new LinkedList();
while (rs.next()) {
SomeObject object = new SomeObject(rs.getString(1), rs.getLong(2));
result.add(object);
List<AnotherObject> otherObjects = anotherRepository.findAllById(object.getId);
object.setOtherObjects(otherObjects);
// and so on
}
return result;
}
}
Okey so after reading Dmytro Polivenok answer I have changed to RowMapper interface instead and I am currently using the other repositories to populate all associations like I show in my example. Is this a good way of doing it?
I think a good practice for Spring JDBC and SQL queries in general is to use one query for each entity.
E.g. assume this model:
Customer (customerId, name, age, ...)
Address (customerId, type, street, city, ...)
PaymentOption (customerId, cardnumber, cardtype, ...)
Customer 1---* Address
Customer 1---* PaymentOption
I would build 3 queries, 3 Daos, 3 ResultSetExtractors/RowcallbackHandlers:
CustomerDao with readCustomerData(Customer or List)
AddressDao with readAddressForCustomer(Customer or List)
PaymentOptionDao with readPaymentOptionsForCustomer(Customer or List)
If you would bake this in 1 query, you would have to build some logic to revert the cartasian product.
I.e. if the customer has 3 addresses and 2 payment options the query would return 6 rows.
This gets quite hard, if Address or PaymentOption does not have an own primary key.
For many to many:
Customer * --recommends-- * Product
I would probably build:
CustomerDao.readRecommendationsAndProductKeys
getDistinctListOfProductKeysFromRecommendations
ProductDao.readProducts
replaceProductKeysByProductsOnRecommendations
Like this you could reuse ProductDao.readProducts for
Customer * --buys-- * Product or
ProductGroup 1---* Product
I think that your code will work, but the concern here is about usage of ResultSetExtractor which is mainly for JDBC framework itself, and for most cases documentation recommends to use RowMapper.
So alternative approach would be to have method in your DAO that selects and maps parent object. Then for each object to invoke other Repository or private method that selects and maps child objects, and then to link child objects with parents based on your relationship type (one-directional or bidirectional). This approach may also allow you to control whether you want to load child objects or not.
For example, you may check Spring PetClinic application which has SimpleJdbcClinic class
If you can use other frameworks, you may consider mybatis, it is more about mapping and allows you to control your SQL code.