Save selected fields in MongoRepository spring without affecting others - java

There is a mongo collection with lots of nested fields in it.
Since it would be too tedious to map each member in #Data classes. I had added just the members I need to use
#Data
#Document
public class MyCollection {
#Id
private String id;
String code;
String code;
}
My MongoRepository class looks like this
#Repository
public interface MyCollectionRepository extends MongoRepository {
MyCollection findById(String id);
}
Now I want to update the code field in my collection.
So I updated the code field in the entity and did
myCollection = mongoRepository.findById(1)
myCollection.setCode("newCode")
mongoRepository.save(myCollection)
Now problem is all the other fields in the database that I did not include in my entity got removed.
How can I update a selected field without affecting the others. I don't want to individually map all the members of the collection to my #Data class as it is too huge.

You need to create custom repository and in implementation of that repository, use mongoTemplate to update selected fields like this.
Query query = Query.query(Criteria.where("id").is(1));
Update update = new Update();
update.set("code", "newCode");
mongoTemplate.updateFirst(query, update, MyCollection.class);
This will use MongoDb's $set operator to set only provided fields without modifying other fields.

Related

JPA Query with several different #Id columns

Problem
To make my code cleaner i want to introduce a generic Repository that each Repository could extend and therefore reduce the code i have to have in each of them. The problem is, that the Ids differ from Class to Class. On one (see example below) it would be id and in the other randomNumber and on the other may even be an #EmbeddedId. I want to have a derived (or non derived) query in the respository that gets One by id.
Preferred solution
I Imagine having something like:
public interface IUniversalRepository<T, K>{
#Query("select t from # {#entityName} where #id = ?1")
public T findById(K id);
}
Ecample Code
(that does not work because attribute id cannot be found on Settings)
public interface IUniversalRepository<T, K>{
//should return the object with the id, reagardless of the column name
public T findById(K id);
}
// two example classes with different #Id fields
public class TaxRate {
#Id
#Column()
private Integer id;
...
}
public class Settings{
#Id
#Column() //cannot rename this column because it has to be named exactly as it is for backup reason
private String randomNumber;
...
}
// the Repository would be used like this
public interface TaxRateRepository extends IUniversalRepository<TaxRate, Integer> {
}
public interface SettingsRepository extends IUniversalRepository<TaxRate, String> {
}
Happy for suggestions.
The idea of retrieving JPA entities via "id query" is not so good as you might think, the main problem is that is much slower, especially when you are hitting the same entity within transaction multiple times: if flush mode is set to AUTO (with is actually the reasonable default) Hibernate needs to perform dirty checking and flush changes into database before executing JPQL query, moreover, Hibernate doesn't guarantee that entities, retrieved via "id query" are not actually stale - if entity was already present in persistence context Hibernate basically ignores DB data.
The best way to retrieve entities by id is to call EntityManager#find(java.lang.Class<T>, java.lang.Object) method, which in turn backs up CrudRepository#findById method, so, yours findByIdAndType(K id, String type) should actually look like:
default Optional<T> findByIdAndType(K id, String type) {
return findById(id)
.filter(e -> Objects.equals(e.getType(), type));
}
However, the desire to place some kind of id placeholder in JQPL query is not so bad - one of it's applications could be preserving order stability in queries with pagination. I would suggest you to file corresponding CR to spring-data project.

Aggregate to JPA Entity mapping

In a DDD-project I'm contributing to, we're seeking for some convenient solutions to map entity objects to domain objects and visa versa.
Developers of this project agreed to fully decouple domain model from data model.
The data layer uses JPA (Hibernate) as persistence technology.
As we all reckon that persistence is an implementation detail in DDD, from a developers' point of view we're all seeking for the most appropriate solution in every aspect of the application.
The biggest concern we're having is when an aggregate, containing a list of entities, is mapped to a JPA entity that in it's turn contains a one-to-many relationship.
Take a look at the example below:
Domain model
public class Product extends Aggregate {
private ProductId productId;
private Set<ProductBacklogItem> backlogItems;
// constructor & methods omitted for brevity
}
public class ProductBacklogItem extends DomainEntity {
private BacklogItemId backlogItemId;
private int ordering;
private ProductId productId;
// constructor & methods omitted for brevity
}
Data model
public class ProductJpaEntity {
private String productId;
#OneToMany
private Set<ProductBacklogItemJpaEntity> backlogItems;
// constructor & methods omitted for brevity
}
public class ProductBacklogItemJpaEntity {
private String backlogItemId;
private int ordering;
private String productId;
// constructor & methods omitted for brevity
}
Repository
public interface ProductRepository {
Product findBy(ProductId productId);
void save(Product product);
}
class ProductJpaRepository implements ProductRepository {
#Override
public Product findBy(ProductId productId) {
ProductJpaEntity entity = // lookup entity by productId
ProductBacklogItemJpaEntity backlogItemEntities = entity.getBacklogItemEntities();
Set<ProductBacklogItem> backlogItems = toBackLogItems(backlogItemEntities);
return new Product(new ProductId(entity.getProductId()), backlogItems);
}
#Override
public void save(Product product) {
ProductJpaEntity entity = // lookup entity by productId
if (entity == null) {
// map Product and ProductBacklogItems to their corresponding entities and save
return;
}
Set<ProductBacklogItem> backlogItems = product.getProductBacklogItems();
// how do we know which backlogItems are: new, deleted or adapted...?
}
}
When a ProductJpaEntity already exists in DB, we need to update everything.
In case of an update, ProductJpaEntity is already available in Hibernate PersistenceContext.
However, we need to figure out which ProductBacklogItems are changed.
More specifically:
ProductBacklogItem could have been added to the Collection
ProductBacklogItem could have been removed from the Collection
Each ProductBacklogItemJpaEntity has a Primary Key pointing to the ProductJpaEntity.
It seems that the only way to detect new or removed ProductBacklogItems is to match them by Primary Key.
However, primary keys don't belong in the domain model...
There's also the possibility to first remove all ProductBacklogItemJpaEntity instances (which are present in DB) of a ProductJpaEntity, flush to DB, create new ProductBacklogItemJpaEntity instances and save them to DB.
This would be a bad solution. Every save of a Product would lead to several delete and insert statements in DB.
Which solution exists to solve this problem without making too many sacrifices on Domain & Data model?
You can let JPA/Hibernate solve problem for you.
public void save(Product product) {
ProductJpaEntity entity = convertToJpa(product);
entityManager.merge(entity);
// I think that actually save(entity) would call merge for you,
// if it notices that this entity already exists in database
}
What this will do is:
It will take your newly created JPA Entity and attach it
It will examine what is in database and update all relations accordingly, with priority given to your created entity (if mappings are set correctly)
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
Entity views can also be updatable and/or creatable i.e. support flushing changes back, which can be used as a basis for a DDD design.
Updatable entity views implement dirty state tracking. You can introspect the actual changes or flush changed values.
You can define your updatable entity views as abstract classes to hide "implementation specifics" like e.g. the primary key behind the protected modifier like this:
#UpdatableEntityView
#EntityView(ProductJpaEntity.class)
public abstract class Product extends Aggregate {
#IdMapping
protected abstract ProductId getProductId();
public abstract Set<ProductBacklogItem> getBacklogItems();
}
#UpdatableEntityView
#EntityView(ProductBacklogItemJpaEntity.class)
public abstract class ProductBacklogItem extends DomainEntity {
#IdMapping
protected abstract BacklogItemId getBacklogItemId();
protected abstract ProductId getProductId();
public abstract int getOrdering();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
Product p = entityViewManager.find(entityManager, Product.class, id);
Saving i.e. flushing changes is easy as well
entityViewManager.save(entityManager, product);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features and for flushing changes, you can define a save method in your repository that accepts the updatable entity view
I believe you need to address the issue in a different way.
It is really hard to determine which has been changed when you have a complex graph of objects. However, there should be someone else (maybe a service) which really knows what have changed in advance.
In fact, I did not see in your question the real business "Service" or a class which address the business logic. This will be the one who can solve this issue. As a result, you will have in your repository something more specific removeProductBacklogItem(BacklogItemId idToRemove) or... addProductBacklogItem(ProductId toProductId, ProductBacklogItem itemToAdd). That will force you to manage and identify changes in other way... and the service will be responsible for.

Implementing model inheritance on springboot have strange data repository behavior

I'm going to create a model that implement inheritance concept. I was thinking that it is possible by creating two (or more) tables (one for the parent class and the other for the child class), then create two (or more) model based on the table.
I currently created a model that acts as a parent class, and made it abstract
#NoArgsConstructor // lombok
#AllArgsConstructor // lombok
#Data // lombok
#Entity(name="Account")
#Inheritance
public abstract class AccountModel {
#Id
#Column(name="username")
private String username;
// Some other fields and getters and setters here
}
and then created child class that extends above class
#NoArgsConstructor // lombok
#Data // lombok
#EqualsAndHashCode(callSuper=true) // lombok
#Entity(name="Administrator")
public class AdministratorModel extends AccountModel {
#Id
#Column(name="username")
private String username;
// some other fields and getters and setters here
}
(currently, the username is used as the join)
and I created two repositories for both models
#Repository
public interface AccountRepository extends JpaRepository<AccountModel, String>{};
#Repository
public interface AdministratorRepository extends JpaRepository<AdministratorModel, String>{};
Then, I tested it by trying to save new AdministratorModel object to AdministratorRepository by using JpaRepository.save() method.
I was expecting that this setting will fill data from the object to both of the tables on the database, and automatically mapped all properties to each table. but, the data is only saved as one record on the parent table, and adding new column (one column is something that may refer to child table, and other column is properties of the child table, without adding the join column 'username'), while leaving the child table empty.
I think that I'm doing it wrong.
Then, how to make it works as expected ?
Should I not used inheritance and save the model manually using two (or more) repositories, or should I only create one repository with a custom query, or is there any other way ?
This kind of inheritance should add non default strategy to #Inheritance annotation.
Changing #Inheritance to #Inheritance(strategy=InheritanceType.JOINED) fixes the problem.
As the documentation says this strategy is:
A strategy in which fields that are specific to a
subclass are mapped to a separate table than the fields
that are common to the parent class, and a join is
performed to instantiate the subclass.

Get single column using Spring data JPA without query

I am just trying to avoid writing query and fetch data based on method name convention. Below is my Entity
#Entity
class Product{
#Id
private Integer productId;
private String productName;
private String productStrategy;
}
I have below repository:
interface ProductRepository extends JPARepository<Product,Integer>{
public Product findByProductStrategy(String productStrategy);
}
Above method and repository is working fine for me. But I am using only productName from the above result. So is there any way using which i can just fetch the productName instead of fetching hole record.
Note: I know, we can achieve it using #Query by writing HQL query or native query. But i want to do it without writing query, just with the method name convention.
Consider using Projections from spring data
Create a projection interface for your entity with the field that you want. In your case, it should look something like
public interface ProductName {
String getProductName();
}
and then change your repository to have the return type of the projection interface itself. Spring will take care of the rest.
public interface ProductRepository extends JPARepository<Product,Integer>{
public ProductName findByProductStrategy(String productStrategy);
}

Specify MongoDb collection name at runtime in Spring boot

I am trying to reuse my existing EmployeeRepository code (see below) in two different microservices to store data in two different collections (in the same database).
#Document(collection = "employee")
public interface EmployeeRepository extends MongoRepository<Employee, String>
Is it possible to modify #Document(collection = "employee") to accept runtime parameters? For e.g. something like #Document(collection = ${COLLECTION_NAME}).
Would you recommend this approach or should I create a new Repository?
This is a really old thread, but I will add some better information here in case someone else finds this discussion, because things are a bit more flexible than what the accepted answer claims.
You can use an expression for the collection name because spel is an acceptable way to resolve the collection name. For example, if you have a property in your application.properties file like this:
mongo.collection.name = my_docs
And if you create a spring bean for this property in your configuration class like this:
#Bean("myDocumentCollection")
public String mongoCollectionName(#Value("${mongo.collection.name}") final String collectionName) {
return collectionName
}
Then you can use that as the collection name for a persistence document model like this:
#Document(collection = "#{#myDocumentCollection}")
public class SomeModel {
#Id
private String id;
// other members and accessors/mutators
// omitted for brevity
}
It shouldn't be possible, the documentation states that the collection field should be collection name, therefore not an expression:
http://docs.spring.io/spring-data/data-mongodb/docs/current/api/org/springframework/data/mongodb/core/mapping/Document.html
As far as your other question is concerned - even if passing an expression was possible, I would recommend creating a new repository class - code duplication would not be bad and also your microservices may need to perform different queries and the single repository class approach would force you to keep query methods for all microservices within the same interface, which isn't very clean.
Take a look at this video, they list some very interesting approaches: http://www.infoq.com/presentations/Micro-Services
I used #environment.getProperty() to read from my application.yml. Like so :
application.yml:
mongodb:
collections:
dwr-suffix: dwr
Model:
#Document("Log-#{#environment.getProperty('mongodb.collections.dwr-suffix')}")
public class Log {
#Id
String logId;
...

Categories