Room | Many to Many relation additional fields - java

I am working on a nutrition tracking app. For that I am trying to setup a many to many relation with some additional fields using Room library. I am not sure yet if its even possible?
The use case I am having trouble with is that I want to retrieve a list of Meals(Breakfast, Lunch, Dinner) along with the foods the user has had along with the portion sizes.
So it will be something like -> Meals would have a List of Item. Item would have (1)Food + (2)portionSize of that food. The classes are as follows:
#Entity
public class Meal {
#PrimaryKey( autoGenerate = true)
#ColumnInfo(name = "MEAL_ID")
private Long mealId;
}
#Entity
public class Food {
#PrimaryKey( autoGenerate = true)
#ColumnInfo(name = "FOOD_ID")
private Long id;
}
//Join Table
#Entity(tableName = "MEAL_FOOD", primaryKeys = {"MEAL_ID", "FOOD_ID"})
public class MealFood {
#ColumnInfo(name = "MEAL_ID")
private Long mealId;
#ColumnInfo(name = "FOOD_ID")
private Long foodId;
private Double portionSize;
}
Now for accessing the data, I am trying something like the following pojo:
public class MealWithFoods {
#Embedded
public Meal meal;
#Relation(parentColumn = "MEAL_ID", entityColumn = "FOOD_ID", entity = Food.class, associateBy = #Junction(MealFood.class))
public List<Item> items;
}
public class Item {
#Embedded
private Food food;
#Relation(parentColumn = "FOOD_ID", entityColumn = "FOOD_ID", projection = {"PORTION_SIZE"}, entity = MealFood.class)
private Double portionSize;
}
DAO:
public interface MealsWithFoodsDao {
#Transaction
#Query("SELECT * FROM MEAL")
public List<MealWithFoods> getMealsWithFoods();
}
I have been trying at it for sometime now. Can someone pls offer a path forward. Thanks.

Related

Stuck in a loop while passing RequestBody in Postman

#Entity
#Table(name = "customers")
public class Customer implements Serializable{
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int custID;
private String custName;
#Id
private String email;
private int phone;
#OneToMany (mappedBy = "customer", fetch = FetchType.LAZY)
private List<Transaction> transaction;
#Entity
#Table(name = "transactions")
public class Transaction implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int transID;
private Date date;
private int amount;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "custID", nullable= false)
private Customer customer;
These are my entities, and I have a method:
#PostMapping("/record-transaction")
public Transaction recordTransaction(#RequestBody Transaction transaction) {
return transactionService.addTransaction(transaction);
}
But when I try to create JSON in postman, I get into a loop where while entering values for transaction, at the end I must enter the Customer object as well and when I am entering customer object at the end I again reach to enter the transaction's values. Its like a never ending loop. Help
I couldn't think of anything to do at all. My mind enters the loop itself.
Decouple your DB entities from your request/response by using an intermediate DTO.
Controller:
#PostMapping("/record-transaction")
public TransactionResponse recordTransaction(#RequestBody TransactionRequest body) {
return TransactionResponse.from(transactionService.addTransaction(
body.getDate();
body.getAmount();
body.getCustomerId();
));
}
TransactionRequest:
public class TransactionRequest {
//don't need ID here it'll be auto generated in entity
private Date date;
private int amount;
private int customerId;
}
TransactionResponse:
public class TransactionResponse {
private int id;
private Date date;
private int amount;
private int customerId;
public static TransactionResponse from(Transaction entity) {
return //build response from entity here
}
}
TransactionService:
//when your entity is lean may as well pass the values directly to reduce boilerplate, otherwise use a DTO
public Transaction addTransaction(Date date, int amount, int customerId) {
Customer customerRepo = customerRepo.findById(customerId).orElseThrow(
() -> new CustomerNotFoundException();
);
Transaction trans = new Transaction();
trans.setDate(date);
trans.setAmount(amount);
trans.setCustomer(customer);
return transactionRepository.save(trans);
}
If you want to embed the customer model inside TransactionResponse or TransactionRequest it'll be fairly easy to do and this solution will produce way nicer contract and swagger docs than a bunch of use case specific annotations in your entity.
In general decoupling you request/response payloads, service dtos and entities from each other results in code with more boilerplate but easier to maintain and without weird unexpected side effects and specific logic.

Java Spring Reactive, returning one Mono<..> from many multiple requests

[Java, Spring Reactive, MongoDB]
I'm currently trying to learn Reactive programming by doing and I found a challenge.
I have db object CategoryDB which looks like this:
#NoArgsConstructor
#Getter
#Setter
#Document(collection = DBConstraints.CATEGORY_COLLECTION_NAME)
class CategoryDB {
#Id
private String id;
private String name;
private String details = "";
#Version
private Long version;
private String parentCategoryId;
private Set<String> childCategoriesIds = new HashSet<>();
}
In a service layer I want to use model object Category.
#Getter
#Builder
public class Category {
private String id;
private String name;
private String details;
private Long version;
private Category parentCategory;
#Builder.Default
private Set<Category> childCategories = new HashSet<>();
}
I want to create Service with method Mono<Category getById(String id). In this case I want to fetch just one level of childCategories and direct parent Category. By default repository deliver Mono findById(..) and Flux findAllById(..) which I could use, but I'm not sure what would be the best way to receive expected result. I would be grateful for either working example or directions where can I find solution for this problem.
I've spent some time to figure out solution for this problem, but as I'm learning I don't know if it's good way of solving problems.
Added some methods to Category:
#Getter
#Builder
public class Category {
private String id;
private String name;
private String details;
private Long version;
private Category parentCategory;
#Builder.Default
private Set<Category> childCategories = new HashSet<>();
public void addChildCategory(Category childCategory) {
childCategory.updateParentCategory(this);
this.childCategories.add(childCategory);
}
public void updateParentCategory(Category parentCategory) {
this.parentCategory = parentCategory;
}
}
Function inside service would look like this:
#Override
public Mono<Category> findById(String id) {
return categoryRepository.findById(id).flatMap(
categoryDB -> {
Category category = CategoryDBMapper.INSTANCE.toDomain(categoryDB);
Mono<CategoryDB> parentCategoryMono;
if(!categoryDB.getParentCategoryId().isBlank()){
parentCategoryMono = categoryRepository.findById(categoryDB.getParentCategoryId());
}
else {
parentCategoryMono = Mono.empty();
}
Mono<List<CategoryDB>> childCategoriesMono = categoryRepository.findAllById(categoryDB.getChildCategoriesIds()).collectList();
return Mono.zip(parentCategoryMono, childCategoriesMono, (parentCategoryDB, childCategoriesDB) -> {
Category parentCategory = CategoryDBMapper.INSTANCE.toDomain(parentCategoryDB);
category.updateParentCategory(parentCategory);
childCategoriesDB.forEach(childCategoryDB -> {
Category childCategory = CategoryDBMapper.INSTANCE.toDomain(childCategoryDB);
category.addChildCategory(childCategory);
});
return category;
});
}
);
}
Where mapper is used for just basic properties:
#Mapper
interface CategoryDBMapper {
CategoryDBMapper INSTANCE = Mappers.getMapper(CategoryDBMapper.class);
#Mappings({
#Mapping(target = "parentCategoryId", source = "parentCategory.id"),
#Mapping(target = "childCategoriesIds", ignore = true)
})
CategoryDB toDb(Category category);
#Mappings({
#Mapping(target = "parentCategory", ignore = true),
#Mapping(target = "childCategories", ignore = true)
})
Category toDomain(CategoryDB categoryDB);
}
As I said I don't know if it's correct way of solving the problem, but it seem to work. I would be grateful for review and directions.

Spring Boot: POST request to entity with ManyToMany relationship

I'm working on a database for adding bands, musicians, instruments, etc.
I have a table 'band' and a table 'musician'. They have a ManyToMany relationship (one band can have many musicians, a musician can be in many bands), with an extra table BandMusician that has an embeddedId BandMusicianId. I did it like this because I want the relationship between bands and musicians to have also other information, like the year the musician joined the band.
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Band {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String genre;
private int year;
#OneToOne(mappedBy = "band")
private Website website;
#OneToMany(mappedBy = "band")
private List<Album> albuns;
#OneToMany(mappedBy = "band")
private List<BandMusician> musicians;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#JsonDeserialize(using = MusicianJsonDeserializer.class)
public class Musician {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonFormat(pattern = "dd-MM-yyyy")
#JsonProperty("DoB")
#Column(name = "date_of_birth")
private LocalDate DoB;
#ManyToMany
#JoinTable(
name = "musician_instruments",
joinColumns = #JoinColumn(name = "musician_id"),
inverseJoinColumns = #JoinColumn(name = "instrument_id")
)
private List<Instrument> instruments = new ArrayList<>();
#OneToMany(mappedBy = "musician")
private List<BandMusician> bands;
public void addInstrument(Instrument instrument) {
this.instruments.add(instrument);
}
}
#Embeddable
#Data
#AllArgsConstructor
#NoArgsConstructor
public class BandMusiciansId implements Serializable{
private static final long serialVersionUID = 1L;
#Column(name = "band_id")
private Long bandId;
#Column(name = "musician_id")
private Long musicianId;
}
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class BandMusician {
#EmbeddedId
private BandMusiciansId id = new BandMusiciansId();
#ManyToOne
#MapsId("bandId")
#JoinColumn(name = "band_id")
private Band band;
#ManyToOne
#MapsId("musicianId")
#JoinColumn(name = "musician_id")
private Musician musician;
private String role;
private int joined;
}
When I receive a POST request to "/musician" I can save a musician. I'm using Jackson to deserialize a request like this:
{
"name": "John the Ripper",
"DoB": "03-12-1965",
"instruments": "voice, guitar",
"bands": "Band1, Band2"
}
With Jackson I can get each band, search with the BandRepository and create a BandMusician.
THE PROBLEM: When I receive the request, in order to create a BandMusician I have to create a BandMusiciansId, and to do that I need the bandId and the MusicianId. But I'm creating the musician right now, so I don't have the musicianId. It is created automatically when I save the musician.
MusicianJsonDeserializer class
public class MusicianJsonDeserializer extends JsonDeserializer<Musician>{
private final InstrumentRepository instrumentRepository;
private final BandRepository bandRepository;
#Autowired
public MusicianJsonDeserializer(
InstrumentRepository instrumentRepository,
BandRepository bandRepository
) {
this.instrumentRepository = instrumentRepository;
this.bandRepository = bandRepository;
}
#Override
public Musician deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JacksonException {
ObjectCodec codec = p.getCodec();
JsonNode root = codec.readTree(p);
Musician musician = new Musician();
musician.setName(root.get("name").asText());
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
musician.setDoB(LocalDate.parse(root.get("DoB").asText(), formatter));
if (root.get("instruments") != null) {
String instrumentList = root.get("instruments").asText();
String[] instrumentArray = instrumentList.split(", ");
List<Instrument> musicianInstrumentList = new ArrayList<>();
for (String instrument : instrumentArray) {
Instrument instrumentFound =
instrumentRepository.findByName(instrument)
.orElseThrow(RuntimeException::new);
// TODO custom exception
musicianInstrumentList.add(instrumentFound);
}
musician.setInstruments(musicianInstrumentList);
}
if (root.get("bands") != null) {
// TODO Stuck here!
What I thought of doing: In my MusicianService, after saving the musician, I can create the BandMusician and the relationship. I think doing this in the Service layer would be a bad choice though.
EDIT: To make it easier to understand, I created a project only with the relevant parts of this one and pushed to github (https://github.com/ricardorosa-dev/gettinghelp).
Again, what I want is to be able to send a POST to "/musician", that will be caught by the MusicianJsonDeserializer, and somehow create a BandMusicianId and BandMusician for each band sent in the request body.
I have the entities Band and Musician and a ManyToMany relationship between them with an association table BandMusician.
What I wanted was to create the entity Musician and the relationship (BandMusician) in the same request.
As far as I can gather it is not possible, because in order to create a record in the association table (BandMusician), I would have to have the musician (I'm creating in this request) already created.
I tried everything just to see if it was POSSIBLE and wasn't able to do it. But even if it was possible, it would be a very bad practice, since it would make the class too tightly coupled.
The clear solution was to create only the Musician with this request, and then send another request to create the connection between Band and Musician.
I also tried to create many entries in the BandMusician table with one request, which was also impossible, because the JsonDeserializer table doesn't seem to accept List<> as a return type. I was trying to avoid making a lot of requests to create the relationship entries (for a musician that is in five bands, for example), but it seems it is better to keep things clear and simple.
I now save one musician-band relationship per request:
{
"musician": "Awesome musician",
"band": "Awesome band",
"role": "guitar",
"joined": 2003
}

How to set value of related entities(objects)

I have two entity that have a relation,The relationship works fine, but how can I set value from one object to another in controller.
#Entity
#Table(name = "material_manu_calculator")
public class MaterialManuCalcu {
#Id
#GeneratedValue
#Column(name = "no")
private int no;
#ManyToOne
#JoinColumn(name = "order_id")
private OrderProductManu orderProductManu;
//.....getters and setters and constructors}
Below is the second Entity
#Entity
#Table(name = "orders_products_manu")
public class OrderProductManu {
#Id
#GeneratedValue
#Column(name = "order_id")
private int orderManuId;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "orderProductManu")
private List<MaterialManuCalcu> materialCalcu = new ArrayList<>();
//.....getters and setters and constructors}
below is the Repository
#Repository
#Transactional
public interface OrderProductManuRepository extends JpaRepository <OrderProductManu, Integer> {
#Query(value ="SELECT *FROM orders_products_manu WHERE orders_products_manu.order_id =?", nativeQuery = true)
public OrderProductManu getOrderProductById(int id);
}
I want to set the value of MaterilaManuCalcu in controller as below
#Controller
public class ProductsController {
#Autowired
private OrderProductManuRepository orderRepo;
OrderProductManu orderProduct = orderRepo.getOrderProductById(1);
MaterialManuCalcu manCalc = new MaterialManuCalcu();
manCalc.setOrderProductManu(orderProduct.getOrderManuId());
// I get the error says:
// The method setOrderProductManu(OrderProductManu) in
// the type MaterialManuCalcu is not applicable for the arguments (int)
Update: Constructors
public MaterialManuCalcu(int no, int amountOrdered, int amountAvailable, int amountWillRemain,
MaterialManu materialmanu, OrderProductManu orderProductManu) {
this.no = no;
this.amountOrdered = amountOrdered;
this.amountAvailable = amountAvailable;
this.amountWillRemain = amountWillRemain;
this.materialmanu = materialmanu;
this.orderProductManu = orderProductManu;
}
Another one
public OrderProductManu(int orderManuId, String customerName, int amountOrderedManu, String dateOrdered, Users users,
ProductsManu productsManu) {
this.orderManuId = orderManuId;
this.customerName = customerName;
this.amountOrderedManu = amountOrderedManu;
this.dateOrdered = dateOrdered;
this.users = users;
this.productsManu = productsManu;
}
Update:Showing how both entities are created
For : OrderProductManu
OrderProductManu orderProduct = new OrderProductManu();
orderProduct.setDateOrdered("2021-04-14");
orderProduct.setAmountOrderedManu(platenum);
orderProduct.setCustomerName("Wapili Mteja");
orderProduct.setUsers(userMoja.get(0));
orderProduct.setProductsManu(typeofProduct);
orderProductManus.setOrderManuId(007);//this is the value that I want to set inside
//MateriaManCalcu entity for property setOrderProductManu
//You can check the relationship above
For: MaterialManuCalcu
MaterialManuCalcu manCalc = new MaterialManuCalcu();
manCalc.setAmountAvailable(availableSheets);
manCalc.setAmountOrdered(sheetsNum);
manCalc.setAmountWillRemain(sheetWillRemain);
manCalc.setMaterialmanu(materialSheet);
manCalc.setOrderProductManu(orderProduct);//doing this the whole object of
//orderProduct entity goes inside a one column in our MatrialManuCalcuof entity
Table:material_manu_calculator
How should I do this correctly. Thanks in advance.
You are trying to set id of orderProduct which is returned by calling orderProduct.getOrderManuId() of type int to variable of type OrderProductManu.
Just pass your orderProduct like this manCalc.setOrderProductManu(orderProduct)

Retrieving list of interface object from database using ormLite

I am trying to make a database with these models
Report model
#DatabaseTable(tableName = "report")
public class Report {
#DatabaseField(generatedId = true)
private int id;
#DatabaseField(columnName = "client_name")
private String client;
#ForeignCollectionField(maxEagerLevel = 2, eager = true)
private Collection<Product> productList;
Product model
#DatabaseTable(tableName = "product")
public abstract class Product implements Comparable<Product> {
#DatabaseField(generatedId = true)
private int id;
#DatabaseField(foreign = true)
private Report report;
#DatabaseField(columnName = "instance")
private String instance;
#DatabaseField(columnName = "product_type")
private String productType;
So I am able to successfully insert in the tables,
but when trying out:
List<Report> reports = reportDao.queryBuilder().where().eq("client_name", clientName).query();
I get an exception
Could not create object for class model.product.Product
So it makes sense, because Product is abstract, but is there a way using the "Instance" attribute from Product interface which indicates what kind of implementation of product it is, to create the appropriate object while running the query?
Thank you

Categories

Resources