I wrote a few request mappings and connected my Spring Boot application to a Postgresql DB using JPA. However, when I try to call the API, I get the following message:
{"timestamp":"2021-01-30T21:58:34.028+00:00","status":404,"error":"Not Found","message":"","path":"/api/v1/sessions"}. I tried printing a message when the API is called and it works so I think it might have something to do with the JPA connection? (I also tested if the DB and credentials are good using SQL Shell, and they are ok)
My model:
#Entity(name = "sessions")
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long session_id;
private String session_name;
private String session_description;
private Integer session_length;
#ManyToMany
#JoinTable(
name = "session_speakers",
joinColumns = #JoinColumn(name = "session_id"),
inverseJoinColumns = #JoinColumn(name = "speaker_id"))
private List<Speaker> speakers;
My controller:
#Controller
#RequestMapping("/api/v1/sessions")
public class SessionsController {
#Autowired
private SessionRepository sessionRepository;
#GetMapping
public List<Session> list() {
System.out.println("Get method called");
return sessionRepository.findAll();
}
#GetMapping
#RequestMapping("{id}")
public Session get(#PathVariable Long id) {
return sessionRepository.getOne(id);
}
My repository:
public interface SessionRepository extends JpaRepository<Session, Long> {
}
And lastly, my application properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/databaseName
spring.datasource.username=postgres
spring.datasource.password=mypasswordhere
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.show-sql=true
#GetMapping(value = "/{id}")<---remove Request mapping and also add the slash here
public Session get(#PathVariable Long id) {
return sessionRepository.getOne(id);
}
Related
Im trying to save data to the database, but instead of it JPA is saving null to the database, I am usually doing it with dto, but since it s a very small project, I want to do it without it
Entity
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String department;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_COURSE_TABLE",
joinColumns = {
#JoinColumn(name="student_id",referencedColumnName = "id")
}, inverseJoinColumns = {
#JoinColumn(name = "couse_id",referencedColumnName = "id")
})
#JsonManagedReference
private Set<Course> courses;
}
DAO
#Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}
Controller
#RestController
#RequestMapping("/api")
public class StudentCourseController {
private final StudentRepository studentRepository;
private final CourseRepository courseRepository;
public StudentCourseController(StudentRepository studentRepository,
CourseRepository courseRepository) {
this.studentRepository = studentRepository;
this.courseRepository = courseRepository;
}
#PostMapping
public Student addStudent (Student student){
return studentRepository.save(student);
}
}
and in my application.properties
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialec
t
Make sure is deserialization correct in controller method "addStudent" - If You want to pass Student entity in request body, add annotation #RequestBody to method parameter like:
#PostMapping
public Student addStudent (#RequestBody Student student){
return studentRepository.save(student);
}
If You do not do that - there is possibility to null/empty parameter, what can lead to saving nulls into db.
By the way:
Consider using DTO or Request classes to pass entity in/out your REST application - it will help you avoid circular reference in future and problems with de/serialization your entity.
Consider using ResponseEntity instead of returning object to output - method with ResponseEntity should be like:
#PostMapping
public ResponseEntity<Student> addStudent (#RequestBody Student
student){
return ResponseEntity.ok(studentRepository.save(student));
}
I'm trying to lazily fetch a ManyToMany relationship (Courses - Students) from the Service and pass the result to the Controller. While i'm in the Service, no LazyInitializationException is thrown, thanks to the #Transactional annotation. However, while i'm in the Controller the LazyInitializationException is thrown (while getting Course.students), because the Session was closed. How can i resolve this issue, without eagerly fetch the Collection? That's my code: Couse Model
#Entity
#Getter
#Setter
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany
#JoinTable(name = "COURSES_STUDENTS",
joinColumns = {#JoinColumn(name = "COURSE_ID")},
inverseJoinColumns = {#JoinColumn(name = "STUDENT_ID")})
private Set<Student> students;
public Course() {
this.students = new HashSet<>();
}
Student Model
#Entity
#Getter
#Setter
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToMany(mappedBy = "students")
private Set<Course> courses;
public Student() {
this.courses = new HashSet<>();
}
}
Course Repository
#Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
}
Course Service
#Service
public class CourseService {
private final CourseRepository courseRepository;
#Autowired
public CourseService(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#Transactional
public ResponseEntity<List<Course>> findAll() {
return this.courseRepository.findAll().isEmpty() ? ResponseEntity.noContent().build()
: ResponseEntity.ok(this.courseRepository.findAll());
}
}
Course Controller
#Controller
#RequestMapping("/")
public class CourseController {
private final CourseService courseService;
#Autowired
public CourseController(CourseService courseService) {
this.courseService = courseService;
}
#GetMapping
public ResponseEntity<List<Course>> index() {
return this.courseService.findAll();
}
}
application.properties
spring.datasource.url=jdbc:h2:~/database;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true
spring.h2.console.path=/h2
spring.jpa.open-in-view=false
spring.mvc.hiddenmethod.filter.enabled=true
logging.level.org.springframework.web=DEBUG
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Thanks in advance.
So there are 2 approaches :
What is this spring.jpa.open-in-view=true property in Spring Boot?
This is bad for performance and must be avoided at all costs.
use jpql queries to join fetch lazy collections needed in DAO layer so they are available in the controller when you need them to be.
All in all, do not use transactional to keep the db session open to fetch lazy collections. Just join fetch lazy collections in db / dao layer to have the data needed for each endpoint available.
If you want have a look here for how to use join fetch How to fetch FetchType.LAZY associations with JPA and Hibernate in a Spring Controller
I have springboot rest appplication with Hibernate and MySQL. I have this error:
nested exception is
org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.hql.internal.ast.QuerySyntaxException: Apartment is not
mapped [from Apartment]
But I dont know where I have a mistake. I have two tables: Apartments and Residents in DB. But now I try only getAllApartments() method. I use Intellij and I even checked my DB in her. And I have little picture near my Entity class, where I have correct data source. And I think that I checked names my class and fields.
This is my Entity:
#Entity
#Table(name = "Apartments")
public class Apartment {
#Column(name = "apartment_id")
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer apartmentId;
#Column(name = "apartment_number"
private Integer apartmentNumber;
#Column(name = "apartment_class")
private String apartmentClass;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH},
mappedBy = "apartment")
List<Resident> residentList;
My DAO method:
#Repository
public class ApartmentDAOImpl implements ApartmentDAO {
#Autowired
private EntityManager entityManager;
#Override
public List<Apartment> getAllApartment() {
Session session = entityManager.unwrap(Session.class);
Query query = session.createQuery("from Apartment");
List<Apartment> apartmentList = query.getResultList();
return apartmentList;
}
My Controller:
#RestController
#RequestMapping("/api")
public class ApartmentController {
#Autowired
ApartmentService apartmentService;
#GetMapping("/apartments")
public List<Apartment> getAllApartments() {
List<Apartment> apartmentList = apartmentService.getAllApartment();
return apartmentList;
}
I also have service layer without any logic.
My property.file
spring.datasource.url=jdbc:mysql://localhost:3306/correct_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=projuser
spring.datasource.password=projuser
Give me advice, please.
Maybe, As I used multi module application, Hibernate or Spring didn't see my Entity.
And I clearly indicated my Entity class with #EntityScan(basePackages = {"com.punko.entity"}) under my SpringBootApplication class:
#SpringBootApplication(scanBasePackages = "com.punko")
#EntityScan(basePackages = {"com.punko.entity"})
public class SpringBootApplicationConfig {
public static void main(String[] args) {
SpringApplication.run(SpringBootApplicationConfig.class, args);
}
Given a User entity with the following attributes mapped:
#Entity
#Table(name = "user")
public class User {
//...
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "user_email")
private String email;
#Column(name = "user_password")
private String password;
#Column(name = "user_type")
#Enumerated(EnumType.STRING)
private UserType type;
#Column(name = "user_registered_date")
private Timestamp registeredDate;
#Column(name = "user_dob")
#Temporal(TemporalType.DATE)
private Date dateOfBirth;
//...getters and setters
}
I have created a controller method that returns a user by ID.
#RestController
public class UserController {
//...
#RequestMapping(
value = "/api/users/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<User> getUser(#PathVariable("id") Long id) {
User user = userService.findOne(id);
if (user != null) {
return new ResponseEntity<User>(user, HttpStatus.OK);
}
return new ResponseEntity<User>(HttpStatus.NOT_FOUND);
}
//...
}
A service in my business logic layer.
public class UserServiceBean implements UserService {
//...
public User findOne(Long id) {
User user = userRepository.findOne(id);
return user;
}
//...
}
And a repository in my data layer.
public interface UserRepository extends JpaRepository<User, Long> {
}
This works fine, it returns everything about the user, but I use this in several different parts of my application, and have cases when I only want specific fields of the user.
I am learning spring-boot to create web services, and was wondering: Given the current implementation, is there a way of picking the attributes I want to publish in a web service?
If not, what should I change in my implementation to be able to do this?
Thanks.
Firstly, I agree on using DTOs, but if it just a dummy PoC, you can use #JsonIgnore (jackson annotation) in User attributes to avoid serializing them, for example:
#Entity
#Table(name = "user")
public class User {
//...
#Column(name = "user_password")
#JsonIgnore
private String password;
But you can see there, since you are not using DTOs, you would be mixing JPA and Jackson annotations (awful!)
More info about jackson: https://github.com/FasterXML/jackson-annotations
Let's say I have 2 have to entities:
#Entity
public class Post {
#NotEmpty
private String title;
#NotEmpty
#Lob
private String html;
#NotEmpty
#Lob
private String text;
#ManyToOne
private Topic topic;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "content_media", joinColumns = {#JoinColumn(name = "content_id")}, inverseJoinColumns = {#JoinColumn(name = "media_id")})
private Set<Media> medias = new HashSet<>();
#CreatedBy
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn
private User createdBy;
#LastModifiedBy
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn
private User lastModifiedBy;
...
}
#Entity
public class Media {
#NotEmpty
private String localPath;
#NotEmpty
private String fileName;
private long fileLength;
private String fileType;
private int focusPointX;
private int focusPointY;
...
}
And I'm exposing them using:
#RepositoryRestController
public interface MediaRepository extends JpaRepository<Media, Long> {
}
#RepositoryRestController
public interface PostRepository extends JpaRepository<Post, Long> {
}
I want these controllers to be secure. Let me explain myself.
If logged in user does not have ROLE_ADMIN, Medias should only be
accessable through posts and /medias/ should return 403 or 404
Only users that have ROLE_USER should be able to create to posts
Only the user that have created the post or the ones that have the ROLE_ADMIN should be able to update post.
Only the users that have ROLE_ADMIN should be able to delete posts
Is there a way to do these using RepositoryRestController and Spring Security or RepositoryRestController is for public resources only and I should write service layer myself using RestController?
Yes you can directly use Spring Security with Spring Data REST. You need to define the security of your routes using Spring Security Configuration as shown below:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().
antMatchers(HttpMethod.POST, "/posts").hasRole("USER").
antMatchers(HttpMethod.DELETE, "/posts/**").hasRole("ADMIN").and().
csrf().disable();
}
}
Repository methods will be secured using Spring Security annotations. e.g.
#RepositoryRestController
public interface PostRepository extends JpaRepository<Post, Long> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN')")
void delete(Long aLong);
}
Code above is just a pointer. You can customize it as per your needs. Here is the link to Spring Data examples repository.
Update
To handle the update of the post by the user who created or by any user who is in ADMIN_ROLE you need to create a controller class and define a method with to handle the update
#RequestMapping(method={RequestMethod.PUT}, value={"posts/{id}"})
public void updatePost(#PathVariable("id") Long id, HttpServletRequest request)
{
//Fetch the authenticated user name
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
}
// Make a database call to verify if the user is owner of the post
Post post = postRepository.getPostByUserName(String username, Long postId);
if (post == null && !request.isUserInRole("ADMIN");) {
//return 403 error code
}
//proceed with the update
}