How to join multiple queryDSL tables - spring

I have some tables and I want to get result using queryDSL join, but haven't found any examples on multiple joins using queryDSL.
I have these tables:
Account table: accountId (PK) | email | password
account_profile table: accountId (PK)(fk to account) | nickname
Community table: articleId (PK) | accountId (fk to account) | title | content
Now I want below JPQL to be queryDSL code
select r from community r join r.account.profile a where a.nickname = :nickname
I have entity metamodels - QAccount, QAccountProfile, QCommunity
Additionally, I have to get the result with pagination, so the query should be called with pageable object.
Here is my work that doesn't work yet.
JPAQuery</*What generic type expected?*/> query = new JPAQuery</*???*/>(entityManager);
Predicate predicate = query.from(QCommunity.community).join(/*join directly accountProfile? or account? is it QEntity or real entity?*/);
// where should I place nickname matching condition ?
...
list = (repository.findAll(predicate, pageable)).getContent();
Where should I place the nickname matching condition?
EDIT: Appended entity information
Account.java
#Entity
#Table(name="account", uniqueConstraints={
#UniqueConstraint(columnNames="account_seq"),
#UniqueConstraint(columnNames="email")
})
#DynamicInsert
#DynamicUpdate
#Data
#EqualsAndHashCode
#ToString(includeFieldNames=true)
#RequiredArgsConstructor(staticName="of")
#NoArgsConstructor
public class Account implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="account_seq", nullable=false, unique=true)
private Integer accountId;
#Column(name="email", nullable=false, unique=true)
#NonNull
private String email;
#NonNull
private String password;
#OneToOne(cascade=CascadeType.ALL, mappedBy="account")
private AccountProfile profile;
#OneToOne(cascade=CascadeType.ALL, mappedBy="account")
private AccountSecurity security;
}
AccountProfile.java
#Entity
#Table(name="account_profile", uniqueConstraints={
#UniqueConstraint(columnNames={"account_seq"}),
#UniqueConstraint(columnNames={"nickname"})
})
#DynamicInsert
#DynamicUpdate
#Data
#EqualsAndHashCode
#ToString(includeFieldNames=true)
#RequiredArgsConstructor(staticName="of")
#NoArgsConstructor
public class AccountProfile implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="account_seq", referencedColumnName="account_seq")
private Account account;
#Column(name="nickname", nullable=false)
#NonNull
private String nickname;
}
Community.java
#Entity
#Table(name="community", uniqueConstraints = {
#UniqueConstraint(columnNames="article_seq")
})
#DynamicInsert
#DynamicUpdate
#Data
#NoArgsConstructor
#EqualsAndHashCode
#ToString(includeFieldNames=true)
public class Community {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="article_seq", nullable=false, unique=true)
private Long articleId;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="account_seq", referencedColumnName="account_seq")
private Account account;
#Column(name="title", nullable=false)
private String title;
#Column(name="content", nullable=false)
private String content;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="reg_dt")
private Date date;
#Column(name="read_cnt", nullable=false)
private int readCount;
#Column(name="attach_url")
private String attachUrl;
#Column(name="attach_filename")
private String attachFileName;
#OneToMany(cascade=CascadeType.ALL, mappedBy="article")
private Set<CommunityReply> replies;
}
EDIT: PROBLEM SOLVED
To help others who is facing the problem like me, I am gonna post my working code. the code is searching any community articles with matching specific nickname.
#PersistenceContext
private EntityManager entityManager;
private List<Community> getList(int pageNo, String keyword, int rowsOnPage){
int offset = (pageNo -1) * rowsOnPage;
int limit = rowsOnPage;
JPAQuery<Community> query = new JPAQuery<Community>(entityManager);
QCommunity qCommunity = QCommunity.community;
QAccount qAccount = QAccount.account;
QAccountProfile qAccountProfile = QAccountProfile.accountProfile;
return query
.from(qCommunity)
.innerJoin(qCommunity.account ,qAccount)
.innerJoin(qAccount.profile, qAccountProfile)
.where(qAccountProfile.nickname.like("%"+keyword+"%"))
.orderBy(qCommunity.articleId.desc())
.offset(offset)
.limit(limit)
.fetch();
}

First of all, declare a custom extended base repository class for QueryDSL queries.
First the interface:
#NoRepositoryBean
public interface ExtendedQueryDslJpaRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
<T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable);
}
And then the implementation:
public class ExtendedQueryDslJpaRepositoryImpl<T, ID extends Serializable>
extends QueryDslJpaRepository<T, ID> implements ExtendedQueryDslJpaRepository<T, ID> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
private EntityManager entityManager;
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public ExtendedQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder(this.path.getType(), this.path.getMetadata());
this.querydsl = new Querydsl(entityManager, this.builder);
this.entityManager = entityManager;
}
#Override
public <T1> Page<T1> findAll(JPQLQuery jpqlQuery, Pageable pageable) {
// Count query
final JPQLQuery<?> countQuery = jpqlQuery;
// Apply pagination
JPQLQuery<T1> query = querydsl.applyPagination(pageable, jpqlQuery);
// Run query
return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
}
}
Define the new class as base for base and repositories in a #Configuration class.
#Configuration
#EnableJpaRepositories(basePackageClasses = ..., repositoryBaseClass = ExtendedQueryDslJpaRepositoryImpl.class)
Your repositories then should extend from the new interface (which of course extends JpaRepository):
#Repository
public interface CommunityRepository extends ExtendedQueryDslJpaRepository<Community, Long> {
}
Then, you can try the following code:
String nickname = "nick";
QAccount account = QAccount.account;
QAccountProfile accountProfile = QAccountProfile.accountProfile;
QCommunity community = QCommunity.community;
JPQLQuery query = new JPAQuery(entityManager);
BooleanBuilder predicate = new BooleanBuilder();
predicate.and(accountProfile.nickname.eq(nickname));
// select r from community r join r.account.profile a where a.nickname = :nickname
query.from(community)
.join(community.account, account)
.join(account.accountProfile, accountProfile)
.where(predicate);
repository.findAll(query, pageable);
Hope that helps.

I found one solution as
QEntity qEntity1 = new QEntity("qEntity1");
QEntity qEntity2 = new QEntity("qEntity2");
so while querying you can use
new JPAQueryFactory(entityManager).from(qSampleBO)
.innerJoin(qEntity1).on(qEntity1.id.eq(qSampleBO.address.id))
.innerJoin(qEntity2).on(qEntity2.id.eq(qSampleBO.secondary_address.id))
...

Related

How Can I mapping DTOs using mapstruct?

I am tring to mapping entity datas to DTOs using mapstruct.
And with these sources, I could map id,title datas.
But the problem is.... I can not map userName using these sources.
How can I resolve this problem??
#Entity // DB와의 연결을 위하여
#Data // getter setter
public class Board {
#Id // id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Size(min=2, max=30)
private String title;
#Length(min=20)
#Lob
#Column(columnDefinition="TEXT", nullable = false)
private String content;
#ManyToOne
#JoinColumn(name="userId", referencedColumnName = "id")
private User user;
}
#Builder
#AllArgsConstructor
#Data
public class BoardListDto {
private Long id;
private String title;
private String userName;
}
#Mapper(componentModel = "spring")
public interface BoardListMapper extends EntityMapper<BoardListDto, Board> {
#Override
#Mapping(target = "userName", source = "user.name.value")
List<BoardListDto> toDtos(List<Board> board);
}
public interface EntityMapper <D, E> {
E toEntity(D dto);
D toDto(E entity);
// Entity업데이트 시 null이 아닌 값만 업데이트 하도록 함.
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void updateFromDto(D dto, #MappingTarget E entity);
List<D> toDtos(List<E> entity);
}
no need to implement toDtos method for this. This code should be enough and Mapstruct will handle the rest alone.
#Mapper(componentModel = "spring")
public interface BoardListMapper extends EntityMapper<BoardListDto, Board> {
#Override
#Mapping(target = "userName", source = "user.name")
BoardListDto toDto(Board board);
}

Spring Controller Returns Object Incompletely

There are three classes (Course, Lesson, User).
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "usr")
#Data
public class User extends RepresentationModel<User> implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String username;
private String password;
#ElementCollection(targetClass = ERole.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
private Set<ERole> roles;
}
#Data
#Entity
#NoArgsConstructor
public class Lesson extends RepresentationModel<Lesson> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String startTime;
private String endTime;
private String dayOfWeek;
#ManyToOne
private User teacher;
}
#EqualsAndHashCode(callSuper = true)
#Data
#Entity
public class Course extends RepresentationModel<Course> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Date startDate;
private Date endDate;
private String name;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> teachers;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<User> students;
private String description;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Lesson> lessons;
}
And also RestController (CoursesController). When accessing the server at /courses, I get the correct server response with all fields
.
#RestController
#RequestMapping("/courses")
public class CoursesController {
private final CourseService courseService;
private final UserService userService;
private final LessonService lessonService;
#Autowired
public CoursesController(CourseService courseService, UserService userService, LessonService lessonService) {
this.courseService = courseService;
this.userService = userService;
this.lessonService = lessonService;
}
#GetMapping
#Operation(
summary = "getAllCourses",
description = "Returns all available courses"
)
public ResponseEntity<Page<Course>> getAllCourses(#PageableDefault(sort = "id", size = 5) Pageable pageable) {
try {
Page<Course> coursePage = courseService.findAll(pageable);
for (Course course : coursePage.getContent())
course.add(linkTo(methodOn(CoursesController.class).getCourse(course.getId().toString())).withSelfRel());
return ResponseEntity.ok(courseService.findAll(pageable));
}
catch (Exception e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}
#GetMapping("/{course-id}")
#Operation(
summary = "getCourse",
description = "Returns course by ID"
)
public ResponseEntity<Course> getCourse(#PathVariable ("course-id") String courseId) {
try {
Course course = courseService.getCourseById(courseId);
course.add(linkTo(methodOn(CoursesController.class).getCourse(courseId)).withSelfRel());
return ResponseEntity.ok(course);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
Why, when requesting a course by ID (GET /courses/{id}), does Spring return an incomplete object (despite the fact that I manually added several teachers, students and lessons)?
I need to get all the fields of my object.
My CourseRepository below.
#Repository
#Transactional
public interface CourseRepository extends JpaRepository<Course, Long> {
}
My CourseService below.
#Service
public class CourseService {
private final CourseRepository courseRepository;
private final LessonRepository lessonRepository;
private final UserRepository userRepository;
#Autowired
public CourseService(CourseRepository courseRepository, LessonRepository lessonRepository, UserRepository userRepository) {
this.courseRepository = courseRepository;
this.lessonRepository = lessonRepository;
this.userRepository = userRepository;
}
public Page<Course> findAll(Pageable pageable) {
return courseRepository.findAll(pageable);
}
public Course createCourse(CourseDto courseDto) {
Course course = new Course(courseDto.getStartDate(), courseDto.getEndDate(), courseDto.getName(), courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Optional<Course> getCourseById(String id) {
return courseRepository.findById(Long.parseLong(id));
}
public Course updateCourse(CourseDto courseDto, String id) {
Course course = courseRepository.findById(Long.parseLong(id)).get();
course.setStartDate(courseDto.getStartDate());
course.setEndDate(courseDto.getEndDate());
course.setName(courseDto.getName());
course.setDescription(courseDto.getDescription());
return courseRepository.saveAndFlush(course);
}
public Page<Lesson> getLessonsByCourse(String courseId, Pageable pageable) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
return new PageImpl<>(new ArrayList<>(course.getLessons()), pageable, course.getLessons().size());
}
public Course addLesson(String courseId, LessonDto lessonDto) {
Course course = courseRepository.findById(Long.parseLong(courseId)).get();
Lesson lesson = new Lesson();
lesson.setStartTime(lessonDto.getStartTime());
lesson.setEndTime(lessonDto.getFinishTime());
lesson.setDayOfWeek(lessonDto.getDayOfWeek());
lesson.setTeacher(userRepository.getUserById(lessonDto.getTeacherId()));
lessonRepository.saveAndFlush(lesson);
System.out.println(lesson);
course.getLessons().add(lesson);
return courseRepository.saveAndFlush(course);
}
public void deleteCourse(String id) {
courseRepository.deleteById(Long.parseLong(id));
}
}
Which I would (or might) expect as well. I would links to be generated for those additional relationshps (at least normally with Spring Data RESt handling this is what would happen). I wonder what happens if you ditch the RepresentationModel from your JPA model and just expose Course then. As stated you don't really want your JPA and HATEOAS stuff to be intertwined. You want to have a specialized projection/dto to expose. WHy does it work for your findAll. well you aren't adding links to it (although you think it does but your findAll executes twice!).
Removed RepresentationModel from User class.
Thx to #M.Deinum

RequestDto keep getting Null in Jpa

I'm trying to make invoice in my project.to make it, I need to POST all info that I already put in the DB. I'm trying to use #RequestBody using by requestDto but it keep getting null.
#Data
#Getter
public class InvoiceRequestDto {
private String note;
private AddressRequest addressRequest;
private BuyerRequest buyerRequest;
private SellerRequest sellerRequest;
private OrderRequest orderRequest;
other request in InvoiceRequestDto also look like this.
#Data
#Getter
public class BuyerRequest {
private String companyName;
private String email;
private String buyerManager;
private String buyerManagerNumber;
private String faxNumber;
this is service, I debugged in here and getting null from all requestDto in InvoiceRequestDto.
#Transactional
public Invoice postInvoice(InvoiceRequestDto invoiceRequestDto) {
try {
Buyer buyerPost = buyerRepository.findByBuyerManager(invoiceRequestDto.getBuyerRequest().getBuyerManager());
Seller sellerPost = sellerRepository.findBySellerManager(invoiceRequestDto.getSellerRequest().getSellerManager());
OrderItem orderPost = orderRepository.getByOrderNumber(invoiceRequestDto.getOrderRequest().getOrderNumber());
Invoice newInvoice = new Invoice(invoiceRequestDto.getNote(), orderPost, buyerPost, sellerPost);
Invoice saved = invoiceRepository.save(newInvoice);
return saved;
} catch (Exception e) {
e.printStackTrace();
return null;
}
this is controller.
#PostMapping("api/order/new")
public ResponseEntity<Long> postInvoice(#RequestBody InvoiceRequestDto invoiceRequestDto){
Long result = invoiceService.postInvoice(invoiceRequestDto).getId();
return ResponseEntity.ok(result);
this is Invoice Entity.
#Getter
#Entity
public class Invoice extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id")
private Long id;
#ManyToOne
#JoinColumn(name = "buyer_id")
private Buyer buyer;
#ManyToOne
#JoinColumn(name = "seller_id")
private Seller seller;
#ManyToOne
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JoinColumn(name = "delivery_id")
private Delivery delivery;
#ManyToOne
#JoinColumn(name = "orderItem_id")
private OrderItem orderItem;
private boolean finalized;
private String note;
#Builder
public Invoice(String note, OrderItem orderPost, Buyer buyerPost, Seller sellerPost){
this.note = note;
this.orderItem = orderPost;
this.buyer = buyerPost;
this.seller = sellerPost;
}
Instead of using external class like this
#Data
#Getter
public class BuyerRequest {
private String companyName;
private String email;
private String buyerManager;
private String buyerManagerNumber;
private String faxNumber;
Try to use inner static classes in your InvoiceRequestDto like below and try again.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class InvoiceRequestDto {
private String note;
private AddressRequest addressRequest;
private BuyerRequest buyerRequest;
private SellerRequest sellerRequest;
private OrderRequest orderRequest;
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class AddressRequest {
// neccessary fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class BuyerRequest {
// neccessary fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class SellerRequest {
// neccessary fields
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class OrderRequest {
// neccessary fields
}
}

Error on saving entity says attribute reference unknown entity

i have created multiple entities having many to one relations when i try to save the user by adding a child book object i am getting an error that the booking's attribute schedule is referencing to an unknown entity. the error is "#OneToOne or #ManyToOne on com.demo.flightmanagement.entity.Booking.schedule references an unknown entity: com.demo.flightmanagement.entity.Schedule"
this is airport entity:
#Entity
public class Airport {
#Id
private int id;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "airport")
private List<Schedule> schedules = new ArrayList<Schedule>();
}
this is schedule entity:
#Entity
public class Schedule {
#Id
private int id;
#OneToMany(mappedBy = "schedule")
private List<Booking> bookings = new ArrayList<Booking>();
#ManyToOne(targetEntity = Airport.class)
#JoinColumn(name="airport_id")
private Airport airport;
}
this is user entity
#Entity
public class User {
#Id
private int id;
private String userName;
private String password;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "user")
private List<Booking> bookings = new ArrayList<Booking>();
}
this is booking entity
#Entity
public class Booking {
#Id
#GeneratedValue
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "schedule_id")
private Schedule schedule;
}
this is main calling class
#SpringBootApplication
public class JwtExamplesApplication {
#Autowired
private UserRepo repo;
#PostConstruct
public void initUsers() {
User u = new User(1, "abc", "def");
Schedule s = new Schedule(22, null);
Booking b = new Booking (u, s);
List<Booking> l = new ArrayList<Booking>();
l.add(b);
u.setBookings(l);
repo.save(u);
}
public static void main(String[] args) {
SpringApplication.run(JwtExamplesApplication.class, args);
}
}
this is user repository for saving
#Repository
public interface UserDao extends JpaRepository<User, BigInteger> {
}

How to count records with where clause in jpa test with latest spring-boot?

I have UserRepository:
public interface UserRepository extends JpaRepository<User, String> {}
The entity:
#Entity
#Table(schema="test", name = "TBL_USERS")
#Builder
#AllArgsConstructor
public class User implements Persistable<String> {
#Id
#Column(name = "ID", columnDefinition = "char")
private String id;
#NotNull
#Column(name = "NAME", columnDefinition = "char", nullable = false)
private String name;
...
}
And in my test I want to count records with certain name like the query:
select count(*) from TBL_USERS where name='John';
#Test
public void testCountSimilarNames() {
...
userRepository.count() ... ?
}
I use latest spring-boot.
You need something like :
public interface UserRepository extends CrudRepository<User , String >{
Integer countByName(String name);
}

Resources