How to manage several entities with JPA? - java

Context
I'm trying to create a simple API to learn Spring Boot and JPA. Informations are stored into H2 database.
I have 2 entities : mission and user.
Several users are assigned to a mission. And users can be assigned to different missions.
So, my class Mission has an attribute private ArrayList<User> users;.
Objective
My goal is to create some requests as :
(GET request) IP/missions/123456/users : Get all users assigned to mission 123456
(PUT request) IP/missions/456789 : Assigned users to mission 456789
So, tied both entities.
Problematic
But I don't know how to store/tie informations relative to mission and user. The good way is to create an "associative table" with the scheme Assignements(id_user, id_mission) ?
Thanks for help!
Edit 1 : Code
Entity Mission
#Entity
public class Mission{
#Id
private String id;
private String name;
private Date start;
private Date end;
private ArrayList<User> users;
private int status;
public Mission() {
}
public Mission(String name, Date start, Date end) {
this.name = name;
this.start = start;
this.end = end;
this.status = 0;
}
// getters and setters
}
Entity User
#Entity
public class User {
#Id
private String id;
private String name;
public User() {
}
public User(String name) {
this.name = name;
}
// getters and setters
}
SQL
I use a SQL script. Currently, script looks like :
INSERT INTO mission (id, name, start, end, status) VALUES ('de7d9052-4961-4b4f-938a-3cd12cbe1f82', 'mission 1', '2019-02-11', '2019-02-13', 0)
INSERT INTO mission (id, name, start, end, status) VALUES ('425e7701-02c6-4de3-9333-a2459eece1c8', 'mission 2', '2019-02-10', '2019-02-15', 0)
Edit 2 : New code (with #ManyToMany)
Entities
#Entity
public class Mission {
#Id
private String missionid;
private String namemission;
private Date start;
private Date end;
#ManyToMany
#JoinTable(name = "mission_user",
joinColumns = #JoinColumn(name = "missionid"),
inverseJoinColumns = #JoinColumn(name = "userid")
)
private Set<User> users = new HashSet<>();
private int status;
public Mission() {}
public Mission(String name, Date start, Date end) {
this.namemission = name;
this.start = start;
this.end = end;
this.status = 0;
}
}
#Entity
public class User {
#Id
private String iduser;
private String nomuser;
#ManyToMany(mappedBy = "users")
private Set<Mission> missions = new HashSet<>();
public User() {}
public User(String nom) {
this.nomuser = nom;
}
}
Repositories
#RepositoryRestResource(collectionResourceRel = "mission")
public interface MissionResource extends JpaRepository<Mission, String> {
...
}
#RepositoryRestResource(collectionResourceRel = "user")
public interface UserResource extends JpaRepository<User, String> {
...
}
Rest Controller
#RestController
#RequestMapping(value = "/missions", produces = MediaType.APPLICATION_JSON_VALUE)
#ExposesResourceFor(Mission.class)
public class MissionRepresentation {
private final MissionResource missionResource;
private final UserResource userResource;
public MissionRepresentation(MissionResource missionResource, UserResource userResource) {
this.missionResource = missionResource;
this.userResource = userResource;
}
// mapping
}

The best would be to implement a joining table, just as you said. That would be something like:
create table mission_user (
mission_id int,
user_id int,
primary key (mission_id, user_id)
)
Then a #ManyToMany using this table as mapping or entity like MissionUser and map both User and Mission as #OneToMany to it.

The mission class will have:
#Entity
public class Mission {
...
#ManyToMany(mappedBy = "missions")
private List<User> users;
...
}
and User class will have:
#Entity
public class User {
...
#ManyToMany(mappedBy = "users")
private List<Mission> missions;
...
}
this will create a table in your database with name something like: missions_uesrs and it will have missionId and userId as only columns.

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.

Jpa Repository in Spring boot app findBy issue

I'm trying to create findBy JpaRepo it's about returning only the data where isDeleted attribute is false.
this is my Service :
public List<Customer> getAllCustomers() {
List<Customer> customers = cutomerRepository.findByIsDeletedFalse();
return customers;
}
and this is my Controller :
#GetMapping("/viewList")
#CrossOrigin("http://localhost:4200/")
public ResponseEntity<List<Customer>> getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers = customerService.getAllCustomers();
if (customers.isEmpty()) {
LOGGER.error("no content ");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
LOGGER.info("calling list of customers");
return new ResponseEntity<>(customers, HttpStatus.OK);
}
and this is customer model :
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "is_deleted")
private boolean isDeleted;
}
but when I run it in postman it's not working and return an error :
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not
exist: boolean = integer Hint: No operator matches the given name
and argument types. You might need to add explicit type casts.
Position: 315
How could I solve this issue?
Looks like the name for your query isn't created right.
However, in this case, the usage of #Query will be much clearer.
Code snippet:
public interface CustomerRepo extends JpaRepository<Customer, Integer> {
List<Customer> findAllByIsDeletedIsFalse();
#Query("from Customer c where c.isDeleted=false")
List<Customer> getAllCustomers();
}
Iinstead of:
cutomerRepository.findByIsDeletedFalse()
You missed one more Is at the name of the method.
Update your Domain:
public class Customer implements Serializable {
private final static long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#Column(name = "serial_number")
private Long serialNumber;
// ...
#Column(name = "is_deleted")
private Boolean isDeleted;
}
JPA fields should be Objects instead of primitives. And entity class should implement Serializable as well.
If the exception will be the same you could try to update #Query:
#Query("from Customer c where c.isDeleted=0")
If pure SQL works for your DB you could use native query:
#Query(
value = "select * from Customer where is_deleted = false",
nativeQuery = true)
List<Customer> getAllCustomers();
It's not working because it doesn't follow the naming conventions for a boolean field. Usually in Java the primitive booleans are named without is prefix and the getter would be using this is prefix.
So in your case your entity class should look like that:
public class Customer {
// ...
#Column(name = "is_deleted")
private boolean deleted;
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
}
Also the naming of the spring repository method should be:
List<Customer> findAllByDeletedIsFalse();
In case you want to use a Boolean reference type you can name your field isDeleted, but then the class would look like that:
public class Customer {
// ...
#Column(name = "is_deleted")
private Boolean isDeleted;
public Boolean getIsDeleted() {
return isDeleted;
}
public void setIsDeleted(Boolean isDeleted) {
this.isDeleted = isDeleted;
}
}
and the repository method:
List<Customer> findAllByIsDeletedIsFalse();
Boolean Java maps a bit datatype column. You are probably using int as datatype in your database.

Mongo Template Aggregate by a date interval in Spring.Mongodb

I need to aggregate some data using java and Mongodb.
So my JS script it's that:
db.post.aggregate([
{$match: {date: {$gte: ISODate("2019-08-28T17:50:09.803Z"), $lte: ISODate("2019-12-03T21:45:51.412+00:00")}}},
{$project: {author: 1, _id: 0}},
{$group: {_id: "$author", count: {$sum:1}}}])
And my Java Code using spring+java is:
Aggregation aggregation =
newAggregation(
match(Criteria.where("date")
.lte(dynamicQuery.getEndDate())
.gte(dynamicQuery.getInitDate())),
project("author").andExclude("_id"),
group("author")
.count()
.as("total"));
AggregationResults<Post> result = mongoTemplate.aggregate(aggregation,Post.class, PostSummary.class);
List<Post> map = result.getMappedResults();
And I need to aggregate the sum of documents by authorId. My code returns the user code and no sum of documents.
And we have 2 Collections:
#EqualsAndHashCode(of = "id")
//TODO: Change the #Data to the properly scenario, we don't need
setters in this class anymore.
#Data
#Document (collection = "user")
//TODO: Change collection name to post, because collection are a group
of elements
public class User {
#Id
private String id;
private String name;
private String username;
#Email
private String email;
private String imageUrl;
private String providerId;
private LocalDateTime firstAccess;
}
Post Document:
#Data
#Document(collection = "post")
//TODO: Change collection name to post, because collection are a group of elements
public class Post {
public static final String AUTHOR_FIELD_NAME = "author";
public static final String DATE_FIELD_NAME = "date";
#Id
private String id;
private String content;
#DBRef(lazy = true)
#Field(AUTHOR_FIELD_NAME)
private User author;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
#Field(DATE_FIELD_NAME)
private LocalDateTime date;
public Post(String content, User author, LocalDateTime date) {
this.content = content;
this.author = author;
this.date = date;
}
}
I have one solution, but I do not know if it is a better solution, so, let's go.
Because the Entity represented inside the Post Domain is one DBRef, the MongoDB does not parse the entire Stringo to the PostSummary.Class
#Getter
#Setter
public class PostSummary {
private User author;
private int count;
}
So, for the solution, I only parse the #Id in the author, and it's working. So If anyone has a better solution we have one solution now.
And should be like this:
#Getter
#Setter
public class PostSummary {
#Id
private User author;
private int count;
}

change discriminator value at runtime?

I'm working with a hierachy object model with a jpa entity persistance support.
Here the classes model:
User class:
#Entity
#Table(name = "user", catalog = "users")
#NamedQueries({
#NamedQuery(...
})
#Inheritance(strategy= InheritanceType.JOINED)
#DiscriminatorColumn(name = "apType", discriminatorType =
DiscriminatorType.STRING, length = 255)
//#DiscriminatorValue("user")
public class User implements Serializable {
#Transient
protected PropertyChangeSupport changeSupport = new
PropertyChangeSupport(this);
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "apType")
private String apType;
#Basic(optional = false)
#Column(name = "name")
private String name;
public UsuariDeClaus() {
this.setApType("user");
}
public Long getId() {
return id;
}
public void setId(Long id) {
Long oldId = this.id;
this.id = id;
changeSupport.firePropertyChange("id", oldId, id);
}
public String getApType() {
return apType;
}
public void setApType(String apType) {
this.apType = apType;
}
public String getName() {
return name;
}
public void setName(String name) {
String oldName = this.name;
this.name = name;
changeSupport.firePropertyChange("name", oldName, name);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener
listener) {
changeSupport.removePropertyChangeListener(listener);
}
}
ApplicationUser class :
#Entity
#Table(name = "applicationuser", catalog = "usuweb793")
#NamedQueries({
#NamedQuery(...
})
public class ApplicationUser extends Users{
#Basic(optional = false)
#Column(name = "nickname", unique=true)
private String nickname;
#Basic(optional = false)
#Column(name = "password")
private String password;
public ApplicationUser() {
super.setApType("ApplicationUser");
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
String oldPassword = this.password;
this.password = password;
changeSupport.firePropertyChange("password", oldPassword, password);
}
public String getNickname() {
return user;
}
public void setNickname(String nickname) {
String oldNickname = this.nickname;
this.nickname = nickname;
changeSupport.firePropertyChange("nickname", oldNickname, nickname);
}
}
and administratorUser class:
#Entity
#Table(name = "administratoruser", catalog = "usuweb793")
#NamedQueries({
#NamedQuery(...})
public class AdministratorUser extends AplicationUser{
public AdministratorUser() {
super.setApType("administratoruser");
}
}
The entity manager creates on mysql database 4 tables:
sequence, user, aplicationuser and administratoruser.
user table:
id name aptype
1 aaa user
2 bbb aplicationuser
3 ccc administratoruser
aplicationuser table:
id nickname password
2 bbbxxxx bbbyyyyy
3 cccxxxx cccyyyyy
administratoruser table:
id
3
Is possible to change the user priviligies without remove an object and create a new one ?
(i would like the id not to change)
Something like:
User user = em.find(1);
New AplicattionUser(user);
user table:
id name aptype
1 aaa aplicationuser
2 bbb aplicationuser
3 ccc administratoruser
aplicationuser table:
id nickname password
1 aaaxxx aaayyyyy
2 bbbxxxx bbbyyyyy
3 cccxxxx cccyyyyy
administratoruser table:
id
3
According to what I understand and from my POV, if Application and Administrator are just roles for Userthat you can switch between .... remove the inheretence, make a User entity and Role entity -with a corresponding table and join between the two entities with the appropriate join -OneToOne or OneToMany according to to your business case- ..... If you can't change the database/the code ... there is a "dirty" solution (and "dirty" again) that you might try ... make a native bulk update statement to make the change you need but be aware of the following :
1- I am nit sure if it will work, you need to try it
2- You must be sure that the entity is not managed by any persistence context at the time of running
3- You are responsible for refreshing the entity / persistence context/ cache after the update

Android Room - Handling List of Objects in an Object and querying result

Im working with Android's Room Database and am having some big problems understanding how to:
When I add a Person to the database, it adds all its variables e.g. List<Shoe>,List<Pet> etc. to the database as well.
Create relations such that when I retrieve a Person, all its fields are retrieved e.g Pet, Shoe, Shirt etc. (Not sure what type of query that is)
Perform simple query e.g. Retrieve Person whereshoe.name= "boot";
I know you must use Foreign Key relationships with list of objects, otherwise with a single object one can use #Embed or #TypeConverter
Sample code is shown below;
#Entity(tableName = "person")
public class Person {
#PrimaryKey
#NonNull
private String personId;
private List<Pet> pets;
private List<Shoe> shoes;
private List<Shirt> shirts;
}
#Entity(foreignKeys = {
#ForeignKey(
entity = Person.class,
parentColumns = "personId",
childColumns = "personIdFk"
)
})
public class Pet {
String petId;
String name;
String personIdFk; //Foreign key to person
}
#Entity(foreignKeys = {
#ForeignKey(
entity = Person.class,
parentColumns = "personId",
childColumns = "personIdFk"
)
})
public class Shoe {
String shoeId;
String name;
String personIdFk; //Foreign key to person
}
#Entity(foreignKeys = {
#ForeignKey(
entity = Person.class,
parentColumns = "personId",
childColumns = "personIdFk"
)
})
public class Shirt {
String shirtId;
String name;
String personIdFk; //Foreign key to person
}
Room doesn't support this directly due to some potential issues with lazy loading, but with some DAO trickery it is possible. You'll need to handle the insertions explicitly, and to query everything at once you'll need a POJO to wrap it all.
#Entity(foreignKeys = {
#ForeignKey(
entity = PersonEntity.class,
parentColumns = "personId",
childColumns = "personIdFk",
onDelete = CASCADE
)
})
public class Pet {
#PrimaryKey
private String petId;
private String name;
private String personIdFk;
}
#Entity(tableName = "person")
public class PersonEntity {
#PrimaryKey
private String personId;
}
public class Person {
#Embedded
private PersonEntity personEntity;
#Relation(parentColumn = "personId", entityColumn = "personIdFk")
private List<Pet> pets;
}
#Dao
public abstract class PersonDao {
#Insert
protected abstract void insert(PersonEntity personEntity);
#Insert
protected abstract void insert(List<Pet> pets);
#Transaction
public void insert(Person person) {
insert(person.getEntity());
insert(person.getPets());
}
#Query("SELECT * FROM person")
public abstract List<Person> getAll();
}
#Database(entities = {PersonEntity.class, Pet.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract PersonDao personDao();
}
Constructors, getters and setters omitted for brevity.

Categories

Resources