Request
Within spring boot I'd like to run a completely custom query, for example running a query to get the firstname and lastname columns on the table person. BUT where the existence of the table person and the fact that it may have columns firstname and lastname was not known at compile time.
Why I want to do this
I want to do this as within our application because we have a concept of custom fields and custom entities. These have views automatically built over the top of them. At run time I will know what views are available and what columns they have but I will not know that when the application starts (and they may change while the application is running
What I don't want
A query annotation on a crudRepository because that still needs to target a particular object and so can't have dynamic fields or an arbitrary object (unless someone knows how to make a crudRepository do that)
If you have knowledge of the entity and the fields you need at run time, you can use SpringJDBC Templates to construct SQL queries. Below is an example of fetching a Todo item. You can do something similar but passing in the entity name and a collection of fields you would want. Here is a very basic example:
#Autowired
private DataSource dataSource; // Configure this in a class annotated with #Configuration
public Todo fetchWithToDoId(long id) {
Todo record = new JdbcTemplate(dataSource).queryForObject("SELECT * FROM PUBLIC.TODO WHERE todo_id = ?", new Object[]{id}, getRowMapper());
return record;
}
private RowMapper<Todo> getRowMapper() {
return (resultSet, i) -> {
Todo d = new Todo();
d.setUserId(resultSet.getInt("todo_user_id"));
d.setId(resultSet.getInt("todo_id"));
d.setTitle(resultSet.getString("todo_title"));
d.setCompleted(resultSet.getBoolean("todo_completed"));
d.setCreated(resultSet.getTimestamp("todo_created"));
return d;
};
}
If the tables and the columns do not exist then an exception will be thrown and it's up to you to handle them on the server side and present the appropriate view to the client. You would probably expand this to take in as argument an entity and a KeyValuePair data structure to map field to value. When you construct you query then it will present all the fields and their target values.
Related
We are doing a data migration from one database to another using Hibernate and Spring Batch. The example below is slightly disguised.
Therefore, we are using the standard processing pipeline:
return jobBuilderFactory.get("migrateAll")
.incrementer(new RunIdIncrementer())
.listener(listener)
.flow(DConfiguration.migrateD())
and migrateD consists of three steps:
#Bean(name="migrateDsStep")
public Step migrateDs() {
return stepBuilderFactory.get("migrateDs")
.<org.h2.D, org.mssql.D> chunk(100)
.reader(dReader())
.processor(dItemProcessor)
.writer(dWriter())
.listener(chunkLogger)
.build();
Now asume that this table has a manytomany relationship to another table. How can I persist that? I have basically a JPA Entity Class for all my Entities and fill those in the processor which does the actual migration from the old database objects to the new ones.
#Component
#Import({mssqldConfiguration.class, H2dConfiguration.class})
public class ClassificationItemProcessor implements ItemProcessor<org.h2.d, org.mssql.d> {
public ClassificationItemProcessor() {
super();
}
public Classification process(org.h2.d a) throws Exception {
d di = new di();
di.setA(a.getA);
di.setB(a.getB);`
// asking for object e.g. possible via, But this does not work:
// Set<e> es = eRepository.findById(a.getes());
di.set(es)
...
// How to model a m:n?
return d;
}
So I could basically ask for the related object via another database call (Repository) and add it to d. But when I do that, I rather run into LazyInitializationExceptions or, if it was successful sometimes the data in the intermediate tables will not have been filled up.
What is the best practice to model this?
This is not a Spring Batch issue, it is rather a Hibernate mapping issue. As far as Spring Batch is concerned, your input items are of type org.h2.D and your output items are of type org.mssql.D. It is up to you define what an item is and how to "enrich" it in your item processor.
You need to make sure that items received by the writer are completely "filled in", meaning that you have already set any other entities on them (be it a single entity or a set of of entities such as di.set(es) in your example). If this leads to lazy intitialization exceptions, you need to change your model to be eagerly initialized instead, because Spring Batch cannot help at that level.
Assume that we have a persisted Entity object which has 10 variables, if I do for example repository.read(id) or repository.findById(id) I will get back an Entity object with every variable which is set from the repository.
Is there any way using JPAQuery or EntityManager or any other possible way, that I can make the call on the repository and get back the Entity object BUT without a specific variable being fetched as well?
I have tried the following, but it doesnt seem to do anything, still brings the Set within the response:
JPAQuery<Fruit> query = new JPAQuery<>(entityManager);
QFruit fruit = QFruit.Fruit;
Set<Apple> apple = new HashSet<Apple>();
query.select(fruit).from(fruit).where(fruit.id.eq(fruitId))
.createQuery().getParameters().remove(apple);
return query.fetchOne();
You can use any custom POJO to get your results in and specify what is selected.
https://docs.oracle.com/html/E13946_05/ejb3_langref.html#ejb3_langref_constructor
public interface AppleRepository extends CrudRepository<Apple, Long> {
#Query("SELECT new com.mypackage.Apple(a.field1, a.field2) FROM " +
" Apple a ")
List<Apple> findCustomApples();
}
Other way is to make any particular column to be Lazy Loaded. You can do that with annotation.
Eventually I was trying to read specific data from an entry of a Table, because the specific table has so many data it was harassing the performance, thus bringing the whole entity just for 1 or 2 variables was not correct.
Eventually what helped me was Tuple.
Using JPAQuery you have the advantage that you can select specific variables to be brought back from the search.
e.g.
JPAQuery<MyObject > query = new JPAQuery<>(entityManager);
MyObject myObject = QMyObject.MyObject ;
Tuple response = query.select(myObject.id, myObject.version)
.where(myObject.id.eq("12345")).or(myObject.version.eq("12345")).fetchaAll();
Then you can easily retrieve the Tuple object and handle the values as an array.
I'm learning Spring MVC and I want find a car via an id but get in return the name.
In my service class I call a generic method getXXXById. This is something JPA gives me by nature.
I know that I get the whole entity but how can I just receive the corresponding name to the id.
Example: I call getCarById(2) and it gives me back Tesla.
My Table:
id | Name
----------
1 | Ford
2 | Tesla
My Service:
class CarService {
// code ...
public Optional<CarEntity> getCarById(int id) {
return carRepository.findById(id);
}
There are two options to do that.
Making your own query
You could write your own query in JQPL to retrive only names.
For example you could create method like that in your repository.
#Query("select t.name from CarEntity where id = ?1")
public String findNameById(Integer id);
more information on this feature of Spring Data Jpa HERE
Projections
Second option is to make projection. As it is written in documentation
Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to rather project on certain attributes of those types. Spring Data allows to model dedicated return types to more selectively retrieve partial views onto the managed aggregates.
In simple words, it allows you to aggregate your results form queries in some limited set of attributes rather then whole entity.
Specifically for your needs I'd suggest to use first approch, but it is worth to know both.
I'm using Hibernate Envers in my app to track changes in all fields of my entities.
I'm using #Audited(withModifiedFlag=true) annotation to do it.
The records are been correcty recorded at database and the _mod fields correctly indicate the changed fields.
I want to get a particular revision from some entity and the information of what fields have been changed. I'm using the follow method to do it:
List<Object[]> results = reader.createQuery()
.forRevisionsOfEntity(this.getDao().getClazz(), false, true)
.add(AuditEntity.id().eq(id))
.getResultList();
This method returns an list of an object array with my entity as first element.
The problem is that the returned entity doesn't have any information about the changed fields. So, my question is: how to get the information about the changed fields?
I know that this question is a bit old now but I was trying to do this and didn't really find any answers.
There doesn't seem to be a nice way to achieve this, but here is how I went about it.
Firstly you need to use projections, which no longer gives you a nice entity model already mapped for you. You'll still get back an array of Objects but each object in the array corresponds to each projection that you added (in order).
final List<Object[]> resultList = reader.createQuery()
.forRevisionsOfEntity(this.getDao().getClazz(), false, true)
// if you want revision properties like revision number/type etc
.addProjection(AuditEntity.revisionNumber())
// for your normal entity properties
.addProjection(AuditEntity.id())
.addProjection(AuditEntity.property("title")) // for each of your entity's properties
// for the modification properties
.addProjection(new AuditProperty<Object>(new ModifiedFlagPropertyName(new EntityPropertyName("title"))))
.add(AuditEntity.id().eq(id))
.getResultList();
You then need to map each result manually. This part is up to you, but I'm use a separate class as a revision model as it contains extra data to the normal entity data. If you wanted you could probably achieve this with #Transient properties on your entity class though.
final List<MyEntityRevision> results = resultList.stream().map(this::transformRevisionResult)
.collect(Collectors.toList());
private MyEntityRevision transformRevisionResult(Object[] revisionObjects) {
final MyEntityRevision rev = new MyEntityRevision();
rev.setRevisionNumber((Integer) revisionObjects[0]);
rev.setId((Long) revisionObjects[1]);
rev.setTitle((String) revisionObjects[2]);
rev.setTitleModified((Boolean) revisionObjects[3]);
return rev;
}
I have a very simple task,
I have a "User" Entity.
This user has tons of fields, for example :
firstName
age
country
.....
My goal is to expose a simple controller for update:
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(data)
I would like clients to call my controller with updates that might include one or more fields to be updated.
What are the best practices to implement such method?
One naive solution will be to send from the client the whole entity, and in the server just override all fields, but that seems very inefficient.
another naive and bad solution might be the following:
#Transactional
#RequestMapping(value = "/mywebapp/updateUser")
public void updateUser(int userId, String[] fieldNames, String[] values) {
User user = this.userDao.findById(userId);
for (int i=0 ; i < fieldsNames.length ; i++) {
String fieldName = fieldsName[i];
switch(fieldName) {
case fieldName.equals("age") {
user.setAge(values[i]);
}
case fieldName.equals("firstName") {
user.setFirstName(values[i]);
}
....
}
}
}
Obviously these solutions aren't serious, there must be a more robust\generic way of doing that (reflection maybe).
Any ideas?
I once did this genetically using Jackson. It has a very convenient ObjectMapper.readerForUpdating(Object) method that can read values from a JsonNode/Tree onto an existing object.
The controller/service
#PATCH
#Transactional
public DomainObject partialUpdate (Long id, JsonNode data) {
DomainObject o = repository.get(id);
return objectMapper.readerForUpdating(o).readValue(data);
}
That was it. We used Jersey to expose the services as REST Web services, hence the #PATCH annotation.
As to whether this is a controller or a service: it handles raw transfer data (the JsonNode), but to work efficiently it needs to be transactional (Changes made by the reader are flushed to the database when the transaction commits. Reading the object in the same transaction allows hibernate to dynamically update only the changed fields).
If your User entity doesn't contains any security fields like login or password, you can simply use it as model attribute. In this case all fields will be updated automatically from the form inputs, those fields that are not supose to be updated, like id should be hidden fields on the form.
If you don't want to expose all your entity propeties to the presentation layer you can use pojo aka command to mapp all needed fields from user entity
BWT It is really bad practice to make your controller methods transactional. You should separate your application layers. You need to have service. This is the layer where #Transactional annotation belongs to. You do all the logic there before crud operations.