I have this rest point which is used to return IDs:
#GetMapping("{id}")
public ResponseEntity<?> get(#PathVariable String id) {
return contractService
.findById(Integer.parseInt(id))
.map(mapper::toNewDTO)
.map(ResponseEntity::ok)
.orElseGet(() -> notFound().build());
}
DTO:
public class ContractNewDTO {
.....
private Integer terminal_id;
....
}
How I can translate terminal_id into terminal name using second SQL query?
I need something like this:
......map(mapper::toNewDTO) -> get here terminal_id and make another SQL query to find which name relates to this terminal_id and return the terminal name NOT terminal_id.
Can you give me some advice how to do this?
So you want to retrieve terminal_name based on terminal_id.
You have a couple of options:
1) If terminal_name is in the same database table as temrminal_id, then it should be loaded inside ContractNewDTO in your mapper::toNewDTO you can implement the conversion logic which uses terminal_name instead of temrminal_id.
2) If termminal_name is in another table (e.g. terminaldetails table) and you will need all the data from that table, then you can create a mapping (e.g. OneToOne joined on the terminal_id). Here is an excellent writing on how to do OneToOne mapping, from the master himself.
3) Another option is to use #SecondaryTable. Let's say your current entity is TerminalEntity and it has column terminal_id and is mapped to table "terminal". The terminal_name is in another table "terminaldetails".
What you can do is:
#Entity
#Table(name = "terminal")
#SecondaryTable(name = "terminaldetails")
public class TerminalEntity {
#Id
#GeneratedValue
private Long id;
#Column(name = "terminal_id")
private String terminalId;
#Column(name = "terminal_name", table = "terminaldetails")
private String terminalName;
}
Related
I'm very green when it comes to databases. I feel like this is probably a pretty common database problem, I can't seem to find the correct search terms to find my answer.
My issue is "duplicate" rows in a table. I'm trying to save restaurant food menus in a database, and for the most part its working alright. I have a object called RestaurantWeek, which contains a list of RestaurantDay objects and each day contains a list of RestaurantCourse objects. They get saved as such in the database: image. "weeks_days" and "days_courses" tables are the link between the "weeks", "days" and "courses" tables.
Now the problem comes when many days can have the same "course". Almost every single day has "Salad" as a course, so what ends up happening is I have 12 identical rows in my "courses" table, the only exception being the id column: image. So now the question is, how can I tell JPA or Hibernate to use the existing "Salad" row in my "courses" table instead of inserting a new one every time? Is it possible to do this by adding some specific annotation to my objects or their properties?
I have tried setting the "name" property on "RestaurantCourse" to unique with #Column(unique=true) but then I get errors about hibernate trying to save multiple courses with the same name (since name must be unique). I have tried grabbing the "courses" table when saving data and using the same id multiple times, but then I get errors about hibernate trying to save multiple courses with the same id (since id must be unique).
Is it even possible to fix this "easily", such as with few specific annotation I'm in the unknown about? Do I need to change something else about how my data is saved to the database, such as the classes, the annotations, or the way I'm trying to save?
Here are my classes.
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="weeks")
public class RestaurantWeek {
#Id
private long id;
private Date saveDate;
private String weekName;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantDay> days;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name="days")
public class RestaurantDay {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Long id;
private String day;
#OneToMany(cascade = CascadeType.ALL)
private List<RestaurantCourse> courses;
}
#AllArgsConstructor
#NoArgsConstructor
#Data
#TypeDef(name = "list-array", typeClass = ListArrayType.class)
#Entity
#Table(name = "courses")
public class RestaurantCourse {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true)
private String name;
private String price;
private String type;
#Type(type = "list-array")
#Column(name = "tags", columnDefinition = "text[]")
private List<String> tags;
}
And what I'm using to save:
#Repository
public interface RestaurantMenuRepository extends JpaRepository<RestaurantWeek, Long> {
}
public class RestaurantMenuServiceImpl implements RestaurantMenuService {
#Autowired
private RestaurantMenuRepository restaurantMenuRepository;
#Override
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
return this.restaurantMenuRepository.save(restaurantWeek);
}
}
Thanks in advance.
Yes is posible, you must use existing entity. But use in this method
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) parameter RestaurantWeek is not correct try put this method some dto class with need field to create entity class, additionally pass the parameter to available identify courses entity find which you can doing relationship and add to days entity.
No pass all parameter every time!
Alright, finally found the correct search terms and found the answer. Resolution was a combination of serob's answer and a bunch of googling.
In RestaurantDay I changed #OneToMany to #ManyToMany.
I created repository interfaces for RestaurantDay and RestaurantCourse.
When saving the course, I save the courses first, then the days, and finally the week, while grabbing all the new ids.
public RestaurantWeek addNewWeek(RestaurantWeek restaurantWeek) {
for (RestaurantDay day : restaurantWeek.getDays()) {
for (RestaurantCourse course : day.getCourses()) {
RestaurantCourse dbCourse = this.restaurantCourseRepository.findCourseByName(course.getName());
if (dbCourse == null) {
course.setId(this.restaurantCourseRepository.save(course).getId());
}
else {
course.setId(dbCourse.getId());
}
}
this.restaurantDayRepository.save(day);
}
return this.restaurantMenuRepository.saveAndFlush(restaurantWeek);
}
Try #NaturalId, this would make your name an Id for the Course entity:
https://vladmihalcea.com/the-best-way-to-map-a-naturalid-business-key-with-jpa-and-hibernate/
I am trying to insert a list of entities which have one to one relation to another entity. It is possible that the one to one mapped object would be same for many parent entity. I am expecting that the same child entity is referred in foreign keys of parent, but actually duplicate rows are getting created. Here are my Entities.
#Builder
#Entity
public class PaymentInfoType1 {
#Id
Long id;
LocalDate date;
#Column(precision = 15, scale = 2)
BigDecimal amount;
String reference;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "account", referencedColumnName = "id")
Account account;
}
#Builder
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Account {
#Id
Long id;
#EqualsAndHashCode.Include
String name;
#EqualsAndHashCode.Include
String accountId;
}
I am creating a list of PaymentInfoType1 based on the information received from a different system. Each PaymentInfoType1 get created along with its Account, which could have exactly the same info but different objects in realtime.
When i do:
PaymentInfoType1 first = // Created with some logic
Account account1 = // name = sample & accountId = 123
first.setAccount(account1);
PaymentInfoType1 second = // Created with some logic
Account account2 = // name = sample & accountId = 123
second.setAccount(account2);
// Both the above its own account object but the field have exactly same values.
List<PaymentInfoType1> list = List.of(first, second);
repo.saveAll(list);
I was expecting that there will be two rows in PaymentInfoType1 table and one in Account, but found that Account also has two rows. Looks like Equals and HashCode does not have any effect in this case.
How can handle this to not insert duplicate rows when the mapping objects are similar by equals/hashcode.
JPA does nothing with #EqualsAndHashcode (that just generates class methods equals and hashCode).
JPA identifies entities by entity id annotated with #Id (or #EmebeddedId) and this id is also something that can be implemented and checked - and usually also generated (like some db sequence) - in the database level.
If you want to use Account identified by name and accountId on JPA side you need to use #EmbeddedId and #Embeddable and get rid of #Id. This would be something like:
#Embeddable
public class AccountId {
String name;
String accountId; // maybe needs renaming...
}
and then in the Account:
#EmbeddedId
AccountId accountId;
See this for example
So I am trying to implement a single INSERT(and other CRUD operations) method for different entities:
#Table(name = "CLIENT")
public class Client {
#Column(name = "ID", isPrimaryKey = true)
private long id;
#Column(name = "FULL_NAME")
private String name;
#Column(name = "MONEY")
private double money;
//setters and getter
}
In my Service I have:
public void add(Client c){
clients.add(c);
persistent.add(c.getClass());
}
This method add(Client c) will call persistent.add(c.getClass()). This method looks like:
public void add(Class<?> classOfObjectToBeAdded){
PreparedStatement pstmt = null;
queryCreator = new AddQueryCreator(classOfObjectToBeAdded);
String finalQuery = queryCreator.getQuery();
System.out.println(finalQuery);
//add to database with prepared statement
}
Now, because this add method is an INSERT query, I need to dynamically create the INSERT based on the object I give. I have that AddQueryCreator that will create my INSERT. And this is my problem, how do I do this? An example would be: INSERT INTO mydb.CLIENT VALUES(NULL,"John",5000).
I managed to scan the class for the class annotations to get the table's name and I managed to scan the fields and take the annotations + get those values from the annotation:
(...)
public #interface Column {
String name();
boolean isPrimaryKey() default false;
}
(...)
public #interface Table {
String name();
}
For having another entity, ex: Fireman, my INSERT would look like INSERT INTO mydb.FIREMAN VALUES(NULL,"Nick",25,5,...) where 25 is the age and 5 is the department. I put NULL, because that field is the PK and has AUTO INCREMENT.
How do I create that INSERT (and the other CRUD operations) dynamically?
I do not want to use existing ORMs because I am trying to learn stuff.
I had two hibernate entity here with annotation:
#Entity
#Table(name = "CLIENT")
public class Client {
private Long pkClient;
private String name;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="PK_CLIENT")
public Long getPkClient() {
return pkClient;
}
public void setPkClient(Long pkClient) {
this.pkClient = pkClient;
}
#Column(name="NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
...
}
#Entity
#Table(name="ACCOUNT")
public class Account {
private Long pkClientAccount;
private Long fkClient;
private String accountNo;
#Id
#Column(name="PK_CLIENT_ACCOUNT")
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getPkClientAccount() {
return pkClientAccount;
}
public void setPkClientAccount(Long pkClientAccount) {
this.pkClientAccount = pkClientAccount;
}
#Column(name="FK_CLIENT")
public Long getFkClient() {
return fkClient;
}
public void setFkClient(Long fkClient) {
this.fkClient = fkClient;
}
#Column(name="ACCOUNT_NO")
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
...
}
The relationship is one-to-many which a Client has many Account. Table ACCOUNT has foreign key (FK_CLIENT) to table CLIENT's primary key (PK_CLIENT).
I want to perform this query in HQL form:
select * from ACCOUNT a inner join CLIENT b on a.FK_CLIENT = b.PK_CLIENT
This mean, all properties from Account and Client entity will be selected.
Anyone know how to make that query in HQL form?
AFAIK, in HQL we can only select one entity.
Note:
I cannot use #ManyToOne mapping in Account entity because there is already fkClient property and I can't change this because the get/setFkClient has already been used in other places.
The code above has been simplified by removing unrelated parts to make easier to read. If you find a typo, please let me know in the comment section since I typed the code manually.
Yes, you can select several entities with HQL.
Hibernate will return an array of type Object[].
select
account,
client
from Account account, Client client
where account.fkClient = client.pkClient
With Hibernate 5.1, it's now possible to join entities even if the mapping doesn't mirror the database table relationship.
So, this HQL query is valid from Hibernate 5.1:
select
account,
client
from Account account
join Client client on account.fkClient = client.pkClient
Try following HQL
select account from Account account, Client client where account.fkClient = client.pkClient
For more details please refer Hibernate reference documentation
http://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/queryhql.html#queryhql-where
You can use:
select * from TB_1 as a
left join TB_2 as b
on a.ID_TB_1 = b.ID_TB_2 and b.ID_TB_2 is null
I am using a JPA model with two classes. The first one is mapping a table with "dynamic" data, the second one is mapping a table with read-only, reference data.
As an example, I have a Person entity mapping a Person Table, that contains a #OneToOne reference to the Civility entity, which itself maps to the Civility table (2 columns) that only has 3 records in it (Miss, Mrs and Mr).
I wanted to know the best way to write a query on the person entity based on Civility value. For example, what query would I use to get all Person's with civility = Mr?
Thanks.
one way to map reference lookup data is to use the #Enumerated annotation in jpa. You still have to create enumeration with the lookup values, but that's why it's reference data anyway.
For example, I have a rating code, and its a string/varchar value on table.
But can use a enumeration to use it:
#Enumerated(EnumType.STRING)
#Column
public RatingCode getRating() {
return rating;
}
public void setRating(RatingCode rating) {
this.rating = rating;
}
and the enumeration is:
public enum RatingCode {
Core, Star
}
Use a unit test to try all values, and you know it's a safe way to get reference data.
You can still use HQL to pull out the values, and pass the enumeration as the value:
hql = "select r from Rating as r where r.rating = :aEnum"
// and in the call to pass the parameter
qry.setParameter("aEnum", aRatingCode)
The enumeration is a field within the Rating entity:
#Entity
#Table
public class Rating {
private Integer rating_Id;
private RatingCode rating;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column
public Integer getRating_Id() {
return rating_Id;
}
public void setRating_Id(Integer rating_Id) {
this.rating_Id = rating_Id;
}
#Enumerated(EnumType.STRING)
#Column
public RatingCode getRating() {
return rating;
}
public void setRating(RatingCode rating) {
this.rating = rating;
}
}
So I have a profile, that requires a Rating, so I lookup a rating via the enumeration and add it to the profile.
Profile p = new Profile();
RatingServiceI rs = new RatingService()
Rating r = rs.getRating(RatingCode.Core);
p.setRating(r);
You didn't post your entity definitions, so you will need to interpret the code in this answer to match up with your actual models. Also, note that querying the entities themselves, in this case, has nothing to do whether the data in the underlying tables is 'read-only' or not:
final String queryStr = "SELECT p FROM Person p WHERE p.civility.value = :value";
final TypedQuery<Person> query = entityManager.createQuery(queryStr, Person.class);
query.setParameter("value", "Mr");
List<Person> results = query.getResultList();