Spring JPA Projection duplicates parent entity when fetching nested collection with JPQL - json

Quick explanation
While fetching a nested collection from a JPQL query it serializes one parent object for each nested object. The intended behavior would be serializing one single parent object with a single collection/set of objects.
Using Spring DATA/DATA REST
JPQ Query
#Query(
"""
select distinct ticketOrder.id as id, user.name as name, ticket as tickets from TicketOrder ticketOrder
inner join User user on user.id = ticketOrder.user.id
inner join Ticket ticket on ticket.ticketOrder.id = ticketOrder.id
where user.uid = :uid
"""
)
fun findByUserUid(uid: UUID, pageable: Pageable): Page<TicketOrderProjection>
Related Entity Classes
TicketOrder
class TicketOrder(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id", nullable = false)
val id: Long? = null,
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false)
val user: User,
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, updatable = false)
val raffle: Raffle? = null,
#OneToMany(mappedBy = "ticketOrder", cascade = [CascadeType.ALL], orphanRemoval = true, fetch = FetchType.LAZY)
var tickets: MutableList<Ticket> = mutableListOf(),
#OneToOne(fetch = FetchType.LAZY)
var transaction: Transaction?,
#CreationTimestamp
#Column(name = "created_at", updatable = false)
val createdAt: Timestamp? = null,
#UpdateTimestamp
#Column(name = "updated_at")
val updatedAt: Timestamp? = null,
#Column(name = "completed_at", nullable = true)
var completedAt: LocalDateTime? = null,
#Column
var checkoutCode: String? = null,
#Enumerated(EnumType.STRING)
#Column(name = "order_status", nullable = false)
var status: TicketOrderStatus
)
/* ... omitted for brevity */
Ticket
#Entity
class Ticket(
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ticket_id", nullable = false)
val id: Long? = null,
#Column(name = "ticket_number", nullable = false, columnDefinition = "UNIQUE")
val ticketNumber: Int,
#ManyToOne
#JoinColumn
var ticketOrder: TicketOrder? = null,
#ManyToOne
#JoinColumn
var raffle: Raffle
)
User
class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", nullable = false)
val id: Long? = null,
#Column(nullable = false)
val name: String,
#Column(nullable = false)
val phone: Long,
#Column(nullable = false)
val email: String,
#Column(nullable = false, updatable = false)
val uid: UUID = UUID.randomUUID(),
#CreationTimestamp
#Column(name = "created_at", updatable = false)
val createdAt: Timestamp? = null
)
Projections
TicketOrderProjection
#Projection(types = [TicketOrder::class])
interface TicketOrderProjection {
val id: Long
val name: String
val tickets: Set<TicketsProjection>
}
TicketsProjection
#Projection(types = [Ticket::class])
interface TicketsProjection {
#get:Value("#{target.id}")
val id: Int
}
Finally
Response at RestController
{
"content": [
{
"name": "Raphael Heizer",
"id": 10,
"tickets": [
{
"id": 21
}
]
},
{
"name": "Raphael Heizer",
"id": 10,
"tickets": [
{
"id": 22
}
]
},
{
"name": "Raphael Heizer",
"id": 10,
"tickets": [
{
"id": 23
}
]
}
],
"pageable": {
// omitted
}
Expected response
{
"content": [
{
"name": "Raphael Heizer",
"id": 10,
"tickets": [
{
"id": 21,
},
{
"id": 22
},
{
"id": 23
}
]
},
],
"pageable": {
// omitted
}

Related

How to restructure an Object in C# using LINQ?

I have a data set as follows:
[
{
"Id": 1,
"Country": "Uruguay",
"Name": "Foo",
"Status": "Completed",
},
{
"Id": 2,
"Country": "Uruguay",
"Name": "Foo",
"Status": "Completed",
},
{
"Id": 3,
"Country": "Germany",
"Name": "Foo",
"Status": "Completed",
},
]
I want to transform and sort it by Country so that it looks as follows:
[
{
"Country": "Uruguay",
"Details": [
{
"Id": 1,
"Name": "Foo",
"Status": "Completed",
},
{
"Id": 2,
"Name": "Foo",
"Status": "Completed",
},
],
},
{
"Country": "Germany",
"Details": [
{
"Id": 3,
"Name": "Foo",
"Status": "Completed",
},
],
},
],
These are the classes in C#:
public class Countries {
public int Id { get; set; }
public string Country { get; set; }
public string Name { get; set; }
public string Status { get; set; }
}
public class Details {
public int Id { get; set; }
public string Name { get; set; }
public string Status { get; set; }
}
public class CountryList {
public string Country { get; set; }
public List<Details> Details { get; set; }
}
Some of what I have tried looks as followed:
var foo = countries
.GroupBy(x => new Details { Id = x.Id, Name = x.Name, Status = x.Status })
.Select( y => new CountryList
{
// Country = y.Key.
}
var foo = countries
.GroupBy(x => x.Country)
.Select( y => new CountryList
{
// Country = y.Key.
Details = y.GroupBy(a => new Details
{
Id = a.Id,
Name = a.Name,
Status = a.Status
}).ToList()
}
I am having trouble working out how to use LINQ to solve this. I have done a handful of GroupBy operations in the past, but I wasn't able to work this one out. How do I transform my dataset into the desired result?
You do not need second GroupBy
var foo = countries
.GroupBy(x => x.Country)
.Select(y => new CountryList
{
Country = y.Key,
Details = y.Select(a => new Details
{
Id = a.Id,
Name = a.Name,
Status = a.Status
}).ToList()
};
You can take advantage of the .GroupBy() overload that lets you define a resultSelector to create your CountryLists and populate their Details:
var countries = new List<Countries>
{
new() { Id = 1, Country = "Uruguay", Name = "Foo", Status = "Completed" },
new() { Id = 2, Country = "Uruguay", Name = "Foo", Status = "Completed" },
new() { Id = 3, Country = "Germany", Name = "Foo", Status = "Completed" },
};
List<CountryList> countryList = countries
.GroupBy(
c => c.Country,
( country, matches ) => new CountryList()
{
Country = country,
Details = matches.Select(match => new Details
{
Id = match.Id,
Name = match.Name,
Status = match.Status
}).ToList()
})
.ToList();
, ( country, matches ) => new CountryList() { ... } being the resultSelector.
Example fiddle here.
try this
var orig = JsonConvert.DeserializeObject<List<Countries>>(json);
List<CountryList> countries = orig.GroupBy(o => o.Country)
.Select(x => new CountryList {
Country = x.Key,
Details = x.Select(o => new Details {Id=o.Id,Name=o.Name,Status=o.Status} ).ToList()
}).ToList();

hbm2ddl not working for some entity class in old claimed working source code

We have received claimed working source code. In code, all the entity model classes (hbm2java) are auto generated by Hibernate Tools 4.0.0 (checked by generated class java docs). We are trying to perform reverse engineering to generate database schema from models with mysql databse.
MySQL : 5.1.46-community
MySQL Connector : mysql-connector-java-5.1.46
Hibernate : Hibernate3
Here we are facing many errors:
Too many key parts specified; max 16 parts allowed
I am pasting the code of one such Entity class for which we are getting above error
#Entity
#Table(name = "ABC")
public class Abc implements java.io.Serializable {
private AbcId id;
public Abc() {
}
public Abc(AbcId id) {
this.id = id;
}
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "a", column = #Column(name = "A", nullable = false)),
#AttributeOverride(name = "b", column = #Column(name = "B", nullable = false, length = 10)),
#AttributeOverride(name = "c", column = #Column(name = "C", nullable = false, length = 2)),
#AttributeOverride(name = "d", column = #Column(name = "D", nullable = false)),
#AttributeOverride(name = "e", column = #Column(name = "E", nullable = false, precision = 12)),
#AttributeOverride(name = "f", column = #Column(name = "F", nullable = false, precision = 12)),
#AttributeOverride(name = "g", column = #Column(name = "G", nullable = false, length = 400)),
#AttributeOverride(name = "h", column = #Column(name = "H", length = 400)),
#AttributeOverride(name = "i", column = #Column(name = "I", nullable = false, length = 1)),
#AttributeOverride(name = "j", column = #Column(name = "J", nullable = false, length = 2)),
#AttributeOverride(name = "k", column = #Column(name = "K", nullable = false, length = 1)),
#AttributeOverride(name = "l", column = #Column(name = "L", nullable = false, length = 100)),
#AttributeOverride(name = "m", column = #Column(name = "M", length = 100)),
#AttributeOverride(name = "n", column = #Column(name = "N", nullable = false, length = 23)),
#AttributeOverride(name = "o", column = #Column(name = "O", nullable = false, length = 100)),
#AttributeOverride(name = "p", column = #Column(name = "P", length = 23)),
#AttributeOverride(name = "q", column = #Column(name = "Q", length = 100)),
#AttributeOverride(name = "r", column = #Column(name = "R", nullable = false, length = 100)) })
public AbcId getId() {
return this.id;
}
public void setId(AbcId id) {
this.id = id;
}
}
This is a auto generated class by Hibernate Tools 4.0.0

Why is my data getting associated with wrong column in mysql db?

I have two tables connected with a many to many relationship using a composite key:
Table1
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "user_name")
private String userName;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String password;
private String authorization;
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JsonManagedReference
private List<UserProduct> userProducts = new ArrayList<>();
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Orders> orders = new ArrayList<>();
Table2
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private int price;
private double mass;
private double alcohol;
private String picture;
private int amount;
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<UserProduct> userProducts = new ArrayList<>();
#OneToMany(
mappedBy = "orders",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<OrderProduct> orderProducts = new ArrayList<>();
Table with composite key
#Entity
#Table(name = "user_product")
public class UserProduct {
#EmbeddedId
private UserProductId id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("userId")
#JsonBackReference
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("productId")
private Product product;
#Column(name = "amount_of_new_products")
private int amountOfNewProducts;
When I make a REST call to UserProduct table so i can update the product value using this payload:
{
"user": 4,
"product": 2,
"amountOfNewProducts": 32
}
it writes the information depending on what is the user id and not the product id. For this payload it would write in like this:
{
"id": 3,
"name": "Grimbergen Blanche",
"price": 132,
"mass": 0.33,
"alcohol": 6.0,
"picture": "https://i.imgur.com/qIq1OrC.png",
"amount": 502,
"userProducts": [],
"orderProducts": []
},
{
"id": 4,
"name": "Grimbergen Blonde",
"price": 132,
"mass": 0.33,
"alcohol": 6.7,
"picture": "https://i.imgur.com/OnioHd5.png",
"amount": 435,
"userProducts": [
{
"id": {
"productId": 2,
"userId": 4
},
"product": {
"id": 2,
"name": "Lav Premium",
"price": 73,
"mass": 0.33,
"alcohol": 4.9,
"picture": "https://i.imgur.com/T3gCAOE.png",
"amount": 1862,
"userProducts": [],
"orderProducts": []
},
"amountOfNewProducts": 32
}
],
"orderProducts": []
},
So basically even though i passed in 2 as product id the information will be written in product with id 4 just because the users id is 4. Any kind of a clue where I might me messing up would be appreciated.
You need to fix mappedBy = "user" in Product entity. It must be "product", like this:
#OneToMany(
mappedBy = "product",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<UserProduct> userProducts = new ArrayList<>();
Also check that you have correct #JoinColumn in UserProductId (ufortunately, you didn't put code for it to the question).

Spring Data JPA joining two or more entites

I have three entities,
#Entity
public class Deck {
#Id
private int id;
private int number;
private String name;
#OneToMany(mappedBy = "deck")
private Set<Lab> labs;
//getter and setter methods
}
#Entity
public class Lab {
#Id
private int id;
private String name;
#ManyToOne
private Deck deck;
#OneToMany(mappedBy = "lab")
private Set<LabBooking> labBooking;
//getter and setter methods
}
#Entity
public class LabBooking {
#Id
private int id;
private Date startTime;
private Date endTime;
#ManyToOne
private Lab lab;
//getter and setter methods
}
Following is the LabRepository,
public interface LabRepository extends CrudRepository<Lab, Integer>{
#Query("SELECT l FROM Lab l JOIN l.labBooking lb WHERE l.deck.id = :deckId AND lb.startTime > '2016-02-24 15:00:00'")
List<Lab> findLabs(#Param("deckId") int deckId);
}
I am trying to retrieve the list of Labs in a deck which are occupied from a particular time.
When I execute the equivalent query (SELECT * FROM lab l JOIN lab_book lb ON l.id = lb.lab_id WHERE l.deck_id = 9999 AND lb.start_time > '2016-02-24 15:00:00') in MySQL, I am getting the following result
id name deck_id id end_time start_time lab_id
9001 Lab One 9999 5 2016-02-24 17:00:00 2016-02-24 16:00:00 9001
In the spring application I am getting the following result,
[{
"lab_id": 9001,
"lab_name": "Lab One",
"lab_booking": [{
"id": 4,
"start_time": "2016-02-24 15:00:00",
"end_time": "2016-02-24 16:00:00"
}, {
"id": 5,
"start_time": "2016-02-24 16:00:00",
"end_time": "2016-02-24 17:00:00"
}, {
"id": 3,
"start_time": "2016-02-24 14:00:00",
"end_time": "2016-02-23 14:30:00"
}]
}]
The Lab object was supposed to contain only the booking id 5, instead it shows all the ids.
If the sql query return 5 records, then the repository returns 5 Lab objects which are duplicate. What may be the issue?
Seems that your missing distinct.
SELECT distinct l FROM Lab l JOIN l.labBooking lb WHERE l.deck.id = :deckId AND lb.startTime > '2016-02-24 15:00:00'"

Adding information from one JsonBuilder object to another

As the title suggests, I'm trying to add information held in one JsonBuilder object to a second JsonBuilder object.
Currently I have this:
public String buildOneUser(DyveUserDTO user)
{
def userBuilder = new JsonBuilder()
userBuilder user.collect { usr ->
[
'Name': usr.userName,
'Allowance': usr.allowance,
'Total Holidays in Calendar': usr.totalHolidaysInCal,
'Holidays Booked': usr.numHolidaysBooked,
'Holidays Taken': usr.numHolidaysTaken,
'Holidays Remaining': usr.totalHolidaysLeft
]
}
def userHolidayBuilder = new JsonBuilder()
userHolidayBuilder user.holidayEvents.collect { usr ->
[
'Start Date': usr.startDate,
'End Date': usr.endDate,
'Days': usr.days
]
}
def userAndHolidays = userBuilder + userHolidayBuilder
return userAndHolidays.toPrettyString()
}
user.holidayEvents is a list of objects representing holidays and it could be empty or have any number of objects in it. This made me hesitant of doing something like:
def userBuilder = new JsonBuilder()
userBuilder user.collect { usr ->
[
'Name': usr.userName,
'Allowance': usr.allowance,
'Total Holidays in Calendar': usr.totalHolidaysInCal,
'Holidays Booked': usr.numHolidaysBooked,
'Holidays Taken': usr.numHolidaysTaken,
'Holidays Remaining': usr.totalHolidaysLeft
'Holiday': usr.holidayEvents[0].startDate
'Holiday': usr.holidayEvents[0].endDate
'Holiday': usr.holidayEvents[0].days
]
}
As I would only get the amount of holidays I write code for. It would also throw an exception if a user had no holidays and I told it look at usr.holidayEvents[1] as it's outside of the list range.
I've also tried nesting a .collect like this
def userBuilder = new JsonBuilder()
userBuilder {
'Name' user.userName,
'Allowance' user.allowance,
'Total Holidays in Calendar' user.totalHolidaysInCal,
'Holidays Booked' user.numHolidaysBooked,
'Holidays Taken' user.numHolidaysTaken,
'Holidays Remaining' user.totalHolidaysLeft,
'Holidays' user.holidayEvents.collect{ evt ->
[
'Start Date': evt.startDate,
'End Date': evt.endDate,
'Days': evt.days
]
}
}
But this returned all the keys except the Holidays key.
Any help would be greatly appreciated!
EDIT - My code now looks like this:
public String buildOneUser(DyveUserDTO user)
{
def userBuilder = new JsonBuilder()
userBuilder user.collect { usr ->
[
'Name': usr.userName,
'Allowance': usr.allowance,
'Total Holidays in Calendar': usr.totalHolidaysInCal,
'Holidays Booked': usr.numHolidaysBooked,
'Holidays Taken': usr.numHolidaysTaken,
'Holidays Remaining': usr.totalHolidaysLeft,
'Holidays': usr.holidayEvents.collect{ evt ->
[
'Start Date': evt.startDate,
'End Date': evt.endDate,
'Days': evt.days
]
}
]
}
}
EDIT 2 - Sample Code
Method to call:
public String buildOneUser(DyveUserDTO user)
{
def userBuilder = new JsonBuilder()
userBuilder {
Name:
user.userName
Allowance:
user.allowance
TotalHolidaysInCalendar:
user.totalHolidaysInCal
HolidaysBooked:
user.numHolidaysBooked
HolidaysTaken:
user.numHolidaysTaken
HolidaysRemaining:
user.totalHolidaysLeft
Holidays:
user.holidayEvents.collect { evt ->
[
'Start Date': evt.startDate,
'End Date' : evt.endDate,
'Days' : evt.days
]
}
}
return userBuilder.toPrettyString()
}
User to pass in:
class DyveUserDTO
{
String firstName = "Foo"
String userName = "FooBar"
Integer userID = 42
BigDecimal numHolidaysBooked = 3
BigDecimal numHolidaysTaken = 0
BigDecimal totalHolidaysInCal = 3
BigDecimal totalHolidaysLeft = 12
BigDecimal allowance = 12
List<HolidayObject> holidayEvents = []
}
Holiday objects to go in holidayEvents:
class HolidayObject
{
public Integer userID = 42
public String title = "Foo Holiday"
public String event = "Holiday"
public String amPm = "Full Day"
public String name = "Foo"
public LocalDateTime startDate = LocalDateTime.parse(2015-02-20T00:00:00)
public LocalDateTime endDate = LocalDateTime.parse(2015-02-20T00:00:00)
public BigDecimal days = 1
}
class HolidayObject
{
public Integer userID = 42
public String title = "Foo Holiday Pm"
public String event = "Holiday"
public String amPm = "Pm"
public String name = "Foo"
public LocalDateTime startDate = LocalDateTime.parse(2015-02-23T00:00:00)
public LocalDateTime endDate = LocalDateTime.parse(2015-02-24T00:00:00)
public BigDecimal days = 2
}
each just returns the list it's called upon, collect should be used for events. See the working code below:
import groovy.json.JsonBuilder
class UserEvent {
def start
def end
def days
}
class User {
def name
def events
}
def u1 = new User(name: 'u1', events: [new UserEvent(start: 0, end: 1, days: 1), new UserEvent(start: 0, end: 2, days: 2)])
def u2 = new User(name: 'u2', events: [new UserEvent(start: 0, end: 3, days: 3)])
def users = [u1, u2]
def userBuilder = new JsonBuilder()
userBuilder users.collect { usr ->
[
'name': usr.name,
'events': usr.events.collect { e ->
[
start: e.start,
end: e.end,
days: e.days,
]
}
]
}
print userBuilder.toPrettyString()
EDIT
Below is a working example:
import groovy.json.JsonBuilder
user = new DyveUserDTO()
def userBuilder = new JsonBuilder()
userBuilder {
Name user.userName
Allowance user.allowance
TotalHolidaysInCalendar user.totalHolidaysInCal
HolidaysBooked user.numHolidaysBooked
HolidaysTaken user.numHolidaysTaken
HolidaysRemaining user.totalHolidaysLeft
Holidays user.holidayEvents.collect { evt ->
[
'Start Date': evt.startDate,
'End Date' : evt.endDate,
'Days' : evt.days
]
}
}
println userBuilder.toPrettyString()
class DyveUserDTO {
String firstName = "Foo"
String userName = "FooBar"
Integer userID = 42
BigDecimal numHolidaysBooked = 3
BigDecimal numHolidaysTaken = 0
BigDecimal totalHolidaysInCal = 3
BigDecimal totalHolidaysLeft = 12
BigDecimal allowance = 12
List<HolidayObject> holidayEvents = [new HolidayObject(), new HolidayObject()]
}
class HolidayObject {
public Integer userID = 42
public String title = "Foo Holiday"
public String event = "Holiday"
public String amPm = "Full Day"
public String name = "Foo"
public String startDate = '2015-02-20T00:00:00'
public String endDate = '2015-02-20T00:00:00'
public BigDecimal days = 1
}
No colons : needed. See the sample here. Also I have no Joda dependency so replaced with String.