#OneToMany
I have this error "Can't find inverse attribute" while going through the Spring course. In the course there is no issue with this code but I have. Maybe some one could help?
Instructor.class
#Entity
#Table(name = "instructor")
#Getter
#Setter
#NoArgsConstructor
public class Instructor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "instructor_detail_id")
private InstructorDetail instructorDetail;
#OneToMany(mappedBy = "instructor")
private List<Course> courses;
public Instructor(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Override
public String toString() {
return "Instructor{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", instructorDetail=" + instructorDetail +
'}';
}
}
Course.class
#Entity
#Table(name = "course")
#Getter
#Setter
#NoArgsConstructor
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "title")
private String title;
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name = "instructor_id")
private Instructor instructor;
public Course(String title) {
this.title = title;
}
#Override
public String toString() {
return "Course{" +
"id=" + id +
", title='" + title + '\'' +
", instructor=" + instructor +
'}';
}
}
hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hb-03-one-to-many?useSSL=false&serverTimezone=UTC</property>
<property name="connection.username"></property>
<property name="connection.password"></property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">false</property>
<!-- Set the current session context -->
<property name="current_session_context_class">thread</property>
</session-factory>
</hibernate-configuration>
I found that it could be fixed by "inverse" attribute in some POJO mapping xml classes. But I don't use them and in lesson there is no using of this classes too.
Inverse attribute is normally used to let hibernate know which side is the relationship owner. In hibernate, only the 'relationship owner' should maintain the relation, means updating the foreign key column in case of any update. But in your case it is one-to-many bi directional in which case the inverse is by default true for the many side. You can try manually mentioning inverse=true in the courses side though it is not actually necessary.
Related
I send a POST request to create a new user but I get an error: "415 unsupported media type"
I have been reading the documentation and other questions on this topic and by now I have already checked the headers "Content-Type" and other.
I also know for sure that it's not about dependencies and not about configuration. I created a simple TestClass and tested my code on it. Everything works perfectly. From this I conclude that the matter is in my Entity class, but I do not understand what exactly.
This is my rest controller:
#RestController
#RequestMapping("/api/users")
#CrossOrigin(origins = "http://localhost:3000/", maxAge = 3600)
public class UserRestController {
...
#PostMapping()
public ResponseEntity<UserDto> create(#RequestBody User user) {
User result = userService.create(user);
return new ResponseEntity<>(UserDto.fromUser(result), HttpStatus.OK);
}
}
My EntityClass:
#Entity
#Table(name = "users")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User extends BaseEntity {
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Enumerated(EnumType.STRING)
#Column(name = "role")
private Role role;
#Column(name = "active")
private boolean active;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
private List<Restaurant> restaurants;
#OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
private List<Comment> comments;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "vote_id")
#JsonBackReference
private Vote vote;
#Override
public String toString() {
return "User{" +
"id='" + getId() + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", role=" + role +
", active=" + active +
'}';
}
}
And my BaseEntity class:
#MappedSuperclass
#Data
public class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#CreatedDate
#Column(name = "created")
private LocalDateTime created;
#LastModifiedDate
#Column(name = "updated")
private LocalDateTime updated;
}
Here is what I get in Postman:
It turned out that the problem is in the fields that are other objects of my application. The parser didn't know what to do with them. It was enough to ignore them (#JsonIgnore) and everything worked as it should.
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
#JsonIgnore
private List<Restaurant> restaurants;
#OneToMany(mappedBy = "author", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
#JsonIgnore
private List<Comment> comments;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "vote_id")
#JsonBackReference
#JsonIgnore
private Vote vote;
i had scheme of user parking and detail parking.
user can park many times (one to many)
im trying to add detail parking object to my db, but i dont have idea how to add the fk from the user in the row of the table, its gave me null there.
(ignore from the logic of the model, i just want to understood the logic how can i the object with fk of ther entity)
this is my code:
#PostMapping("/parking")
public String saveCarParking(#ModelAttribute("user") parkingUsers parkingUsers) {
// parkingUsers[id, firstName, lastName, license]
parkingUsers p = new parkingUsers("jhon", "nash", "248651355");
parkingUsersService.saveParkingUser(p);
// parkingDetails[id, entryDate, entryTime, exitDate, exitTime, user_id(FK)]
parkingDetails d = new parkingDetails(LocalDate.now(), null, LocalDate.now(), null);
parkingDetailsService.saveParkingUser(d);
//how i connect parkingDetails object with fk of parkingUsers?
//it adding now row of parkingDetails but without the fk of user
return "redirect:/parkingList";
}
parking user entity:
#Entity
#Table(name ="users")
public class parkingUsers {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "license")
private String license;
#OneToMany(mappedBy = "parkingUsers", cascade = CascadeType.ALL, orphanRemoval = true)
private List<parkingDetails> parkingDetails = new ArrayList<parkingDetails>();
public parkingUsers() {
}
public parkingUsers(String firstName, String lastName, String license) {
this.firstName = firstName;
this.lastName = lastName;
this.license = license;
}
//setter gettrs and tostring...
entity class of details parking
#Entity
#Table(name ="details")
public class parkingDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "entry_date")
private LocalDate entryDate;
#Column(name = "entry_time")
private LocalDateTime entryTime;
#Column(name = "exit_date")
private LocalDate exitDate;
#Column(name = "exit_time")
private LocalDateTime exitTime;
#ManyToOne
#JoinColumn(name="user_id")
private parkingUsers parkingUsers;
public parkingDetails() {}
public parkingDetails(LocalDate entryDate, LocalDateTime entryTime, LocalDate exitDate, LocalDateTime exitTime) {
this.entryDate = entryDate;
this.entryTime = entryTime;
this.exitDate = exitDate;
this.exitTime = exitTime;
}
//test
// public parkingDetails(LocalDate entryDate, LocalDateTime entryTime, LocalDate exitDate, LocalDateTime exitTime, int user_id ) {
// this.entryDate = entryDate;
// this.entryTime = entryTime;
// this.exitDate = exitDate;
// this.exitTime = exitTime;
// this.parkingUsers.setId(user_id);
// }
//setter gettrs and tostring...
In the ParkingDetails entity, you can have a setter for "parkingUsers" variable to set user object.
In your REST api's saveCarParking() method, before calling "parkingDetailsService.saveParkingUser(d);" you can pass the user object to ParkingDetails using setter created in ParkingDetails.
This should work. No need to explicitly extract the user_id from user's object to pass into ParkingDetails.
Adding one more parameter of type ‘parkingUsers‘ in the constructor of ‘ ParkingDetails’ to initialize user in parking class will also work.
(Apart, it is a good practice to start the class name with a capital letter e.g. instead of having class name as parkingDetails, it should be ParkingDetails.)
I have this piece of code where the author creates a JPA Entity. Somehow, it's successfully working and that seems weird to me since not every field is annotated with "#Column". Based on that, here's my question:
How it is possible for this class to work properly (all data is being successfully recorded in the database) without every field not having a "Column" annotation (excepting "id")?
/**
* #author Ram Alapure
* #since 05-04-2017
*/
#Entity
#Table(name="User")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
private String firstName;
private String lastName;
private LocalDate dob;
private String gender;
private String role;
private String email;
private String password;
#Override
public String toString() {
return "User [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", dob=" + dob + ", email="
+ email + "]";
}
}
For instance, here's another class which is working properly as well. But this time, with a "#Column" annotation in every field. I thought it was a pre-requisite to JPA create a column from the field.
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "address_id")
private long id;
#Column(name = "rua")
private String street;
#Column(name = "number")
private int number;
#Column(name = "complement")
private String complement;
#Column(name = "suburb")
private String suburb;
#Column(name = "city")
private String city;
#Column(name = "state")
private String state;
#Column(name = "country")
private String country;
#Column(name = "cep")
Getters and Setters were hidden from all pieces of code since they just look like regular getters and setters, not being necessary to be shown.
If you don't specify #Column annotation (Optional) then Hibernate uses default naming stretegy by using camel case.
firstName field becomes first_name column in Database.
You can also define your own naming stretegy according to your needs.
From the documentation.
strategy ; Hibernate 5 defines a Physical and Implicit naming strategies. Spring Boot configures SpringPhysicalNamingStrategy by default. This implementation provides the same table structure as Hibernate 4: all dots are replaced by underscores and camel cases are replaced by underscores as well.
this annotation means create table in database #Entity with all field in class same the name
the spring boot scan this annotation #Entity after scan
is inject and create Bean in IOC with fields
but if used #Column this help for mapping with Table field if change colume name in dataBase
and spring boot when scan component and see this annotation the bean is change with Colume name to name in database becouse we need success mapping with data base
I have followed JPA interface projection process to map native SQL result into a custom DTO but still, I am getting result in AbstractJpaQuery$TupleConverter$TupleBackedMap format. How can I map the result into desired DTO class?
This is the repository class
#Repository
public interface SubjectMarkingCriteriaRepository extends JpaRepository<SubjectMarkingCriteria,Long> {
#Query(
value = "select smc.id as id ,c.class as academicClass,s.subject as subject,mc.criteria as criteria " +
"from subject_marking_criteria smc " +
"join subject s on smc.subject_id = s.id " +
"join marking_criteria mc on smc.marking_criteria_id = mc.id " +
"Join class c on s.class_id = c.id ",
nativeQuery = true)
List<ResultDTO> findAllByParam();
}
DTO interface
public interface ResultDTO{
Long getId();
String getAcademicClass();
String getSubject();
String getCriteria();
}
Entities are :
#Data
#Entity
#Table(name = "subject_marking_criteria")
public class SubjectMarkingCriteria {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="subject_id")
private Long subjectId;
#Column(name="marking_criteria_id")
private Long markingCriteriaId;
}
#Data
#Entity
#Table(name = "subject")
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="subject")
private String subject;
#Column(name="bn_subject")
private String bnSubject;
}
#Data
#Entity
#Table(name = "marking_criteria")
public class MarkingCriteria {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#Column(name="criteria")
private String criteria;
#Column(name="bn_criteria")
private String bnCriteria;
}
Result found as below
results = {ArrayList#12758} size = 2
0 = {$Proxy210#12766} "org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap#1c56a65c"
1 = {$Proxy210#12767} "org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap#31c7eaa"
My User Class looks as follows:
#Entity
#Table(name = "Users")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userID")
private Integer userID;
#Column(name = "username",nullable = false, unique = true)
private String username;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "address")
private String address;
#Column(name = "email")
private String email;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<CreditCard> creditCard;
//Constructor, Getters and Setters
CreditCard Class looks :
#Entity
#Table(name = "CreditCards")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class CreditCard {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "cardID", nullable = false)
private Integer cardID;
#Column(name = "cardName")
private String cardName;
#Column(name = "cardNumber")
private BigInteger cardNumber;
#Column(name = "expirationDate")
private Integer expirationDate;
#Column(name = "securityCode")
private Integer securityCode;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
//Constructor, Getters and Setters
CreditCard Resource:
#RestController
#RequestMapping("/geektext/users")
class CreditCardResource {
#Autowired
CreditCardRepository cardsRepository;
#Autowired
UserRepository userRepository;
//Displays CreditCard By Username Search
#GetMapping("/{username}/cards")
public Optional<CreditCard> getCardsByUsername(#PathVariable String username) throws NotFoundException {
if (!userRepository.findByUsername(username).isPresent()){
throw new NotFoundException("User '" + username + "' not found");
}
return cardsRepository.findById(userRepository.findByUsername(username).get().getUserID());
}
//Creates New Card for User
#PostMapping("/{userID}/cards")
public CreditCard loadCard(#PathVariable String userID, #RequestBody CreditCard creditCard) throws NotFoundException {
return userRepository.findByUsername(userID).map(user -> {creditCard.setUser(user);
return cardsRepository.save(creditCard);
}).orElseThrow(() -> new NotFoundException("User '" + userID + "' not found"));
}
}
There is also a UserResource.java , UserRepository (Interface) and CreditCardRepository) but these do not affect the problem I am having. Please how can I fix getting list of cards for User passing username on url. How can user create New/ More than one CreditCard instead of updating the one he has.
You are trying to get a credit-card using your userID
return cardsRepository.findById(userRepository.findByUsername(username).get().getUserID());
Instead, you could search for your credit-card by user. To do this, you should create a method in the credit-card repository interface.
List<CreditCard> findByUser(User user);
Then call this method from your controller
return cardsRepository.findByUser(userRepository.findByUsername(username).get())
The post method has a similar problem. You are trying to get user by username, but passing the userID. Also you set user to your new credit-card, but you don't add a new credit-card to your user. (And change the name of credit-cards variable in the User class to creditCards)
return userRepository.findByUsername(userID).map(user -> {creditCard.setUser(user);
return cardsRepository.save(creditCard);
}).orElseThrow(() -> new NotFoundException("User '" + userID + "' not found"));
This will be much better. Test it yourself and change something if I wrote something wrong
User user = userRepository.findById(userID);
user.getCreditCards().add(creditCard);
creditCard.setUser(user);
userRepository.save(user);
NotFoundException I guess you can handle by yourself.
Update: I had to create an ID for each credit card since if the same ID is assigned on the creation of each new credit card, then program would treat it like if I was the same one that was trying to be updated.