I am trying to extract data using composite key of other table, but here problem is, I have list of composite key. Below are the table.
#Embeddable
public class IdRangePk implements Serializable {
#Column("START_RANGE")
private String startRange;
#Column("END_RANGE")
private String endRange;
// default constructor
}
#Entity
#Table(name = "ID_RANGE")
public class IdRange {
#EmbeddedId
private IdRangePk idRangePk;
#Column(name = "PARTY_ID")
private String partyId;
#Column("STATUS")
private String status; // expired or active
// constructors, other fields, getters and setters
}
Here, ID_RANGE have composite primary key (START_RANGE, END_RANGE). So same PARTY_ID can have multiple combination of start & end range.
STATUS can be either "EXPIRED" or "ACTIVE".
#Entity
#Table(name = "MESSAGE")
public class Message {
#Id
#Column("MESSAGE_Id")
private String id;
#Column(name = "PARTY_ID")
private String partyId;
#Column("START_RANGE")
private String startRange;
#Column("END_RANGE")
private String endRange;
// default constructor
// constructors, other fields, getters and setters
}
Here, I need to extract the messages having active ranges for a given PARTY_ID. Also MESSAGE_ID is the primary key.
So I divided it into two steps
Extracting active ranges for given party id
#Repository
public interface IdRangeRepo extends JpaRepository<IdRange, IdRangePk> {
List<IdRange> findByPartyIdAndStatus(String partyId, String status);
}
List<IdRange> idRanges = findByPartyIdAndStatus("123", "ACTIVE");
Extracting message from list of active IdRange
List<String> startRange = new ArrayList<>();
List<String> endRange = new ArrayList<>();
idRanges.stream().forEach(range -> {
startRange.add(range.getStartRange());
endRange.add(range.getEndRange())
});
List<Message> activeRangeMessage = findByPartyIdAndStartRangeInAndEndRangeIn("123", startRange, endRange);
#Repository
public interface MessageRepo extends JpaRepository<Message, String> {
List<IdRange> findByPartyIdAndStartRangeInAndEndRangeIn(String partyId, List<String> startRange, List<String> endRange);
}
My second step query is not right, it is extracting more rows than the expected as query is counter by individually field instead of whole (for startRange & endRange which is a composite key). Can someone please help me correct my query or provide an easiest way to extract rows. I have used derived method but #Query will also do.
Related
In my Spring Boot apps, I generally use projection in order to return joined table results. However, I am trying to use Java Stream to map and return nested entities to the corresponding DTOs. Here is an example that I generally encountered:
Note: I setup entity relationship using Hibernate and that part is completely ok. For this reason, I omitted the related code for brevity. Just concentrate on mapping entities to the nested DTOs.
Country has many States, States has many Towns...
Country:
public class Country {
private Long id;
private String name;
#OneToMany(...)
private Set<State> states = new HashSet<>();
}
State:
public class State {
private Long id;
private Long population;
#ManyToOne(...)
private Country country;
#OneToMany(...)
private Set<Town> towns = new HashSet<>();
}
Town:
public class Town {
private Long id;
private String name;
#ManyToOne()
private State state;
}
I want to get Country list with Country Name, Sum of state population belonging to this country and Town list belonging to the country.
For this purpose, I created the following DTOs, but not sure if it is ok and how can I map the necessary data to this DTO in one step (I don't want to go to database 3 times, instead, I just want to map the country list to the corresponding fields (the list has all of these data as I built the relations properly using Hibernate).
#Data
public class CountryResponse {
private Long id;
private String name;
private Set<StateResponse> states;
private Long population;
private Set<TownResponse> towns;
public CountryResponse(Country country) {
this.id = country.getId();
this.name = country.getName();
// ???
this.states = country.getStates.stream().map(StateResponse::new)
.collect(Collectors.toSet());
this.towns = this.states.stream().map(TownResponse::new)
.collect(Collectors.toSet());
}
}
How can I do this? I would also consider using MapStruct if it is better for this scenario?
To get the sum of state populations, you can use the following stream code
country.getStates().stream().map(State::getPopulation).sum();
and the list of towns can be fetched as follows
country.getStates().stream().map(State::getTowns).flatMap(Set::stream).collect(Collectors.toList());
In my application I am using projections to map *Entity objects to simplified or modified versions of the actual record in the database.
However, I have a particular use case where I am required to replace a certain value from one of the nested projections. Since these are interfaces and also get proxied by Spring, I am not sure if what I want is actually possible but to bring it down to one very simpel example:
Assume I have a UserEntity and a User projection. For my User projection I can simply execute:
User user = this.userEntityRepository.findById(userId);
However, if I want to change something, I am not sure if that is possible. Namely, I cannot do something like this:
if (user.getAge() < 18) {
user.setDisplayName(null);
}
Now, I am aware that I could create an anonymous class new User() { .. } and just pass in the values I required but in my case the objects are nested and hence this is not an option.
The question
Is there another way to replace a value, e.g. displayName as above, without using an anonymous class?
Elaborative example
Reading the following is not really necessary but in order to illustrate my issue in more detail I have pseudo-coded an example that shows a bit closer what the problem is in my particular case.
We have a simple UserEntity:
#Entity
#Table(name = "app_user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String firstName;
#Column
private String lastName;
#Column
private Integer age;
// Setter & Getter ..
}
#Entity
#Table(name = "event")
public class EventEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "event")
private List<EventAttendeeEntity> attendees;
// ..
}
We have a table which maps users to events:
#Entity
#Table(name = "attendee")
public class AttendeeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private EventEntity event;
#ManyToOne
private UserEntity user;
// ..
}
Further, we have projections for these entities which we prepare as views for our clients:
/*
* Projection for User
*/
public interface User {
// All the properties ..
}
/*
* Projection for AttendeeEntity
*/
public interface Attendee {
Long getId();
User getUser();
}
/*
* Projection for EventEntity
*/
public interface Event {
Long getId();
String getName();
List<Attendee> getAttendees();
}
In one of the services we fetch UserEvent. Here, let's say, we want to remove the names of all users below 18 and still return userEvent we just fetched.
public Event getEvent(Long id, Boolean anonymize) {
Event event = this.eventRepository.findById(id);
// The "anonymize" is to highlight that I cannot
// simply solve this in a User-projection
if (!anonymize) {
return event;
}
event
.getAttendees();
.stream()
.peek(attendee -> {
User user = attendee.getUser();
if(user.getAge() < 18) {
// Here we create a new user object without a name
User newUser = new User() {
#Override
String getDisplayName() { return null; }
#Override
Integer getAge() { return user.getAge(); }
}
// !! This is where we hit the problem since we cannot
// !! replace the old user object like this
attendee.setUser(newUser);
}
});
return event;
}
One solution is to use SPEL in you projection selector. Please try
public interface Attendee {
Long getId();
#Value("#{target.user.age >= 18 ? target.user : new your.package.UserEntity()}")
User getUser();
}
Replace you.package with the package of UserEntity. Pay attention to put new UserEntity() and not new User(). This way an empty model will be projected as an empty interface User.
You can't use projections to update your code.
As a final note, it's important to remember that projections and excerpts are meant for the read-only purpose.
API Data Rest Projections Baeldung
I'm trying to get a single data from two tables of database. These tables doesn't have foreign keys and no jointables too. I'm using spring-data to retrieve required data for first data set.
I have two data sets that have a common String value, and want to retrieve data from both tables not using jointables or foreign keys, retrieving data from the first data set.
I'm using simple DataRepository interface
import org.springframework.data.jpa.repository.JpaRepository;
public interface DataRepository extends JpaRepository<FirstData, Long> {
DataService getById(Long id);
}
FirstData entity:
#Data
#Entity
#Table(schema = "someschema", name = "firstdata")
public class FirstData {
#Id
#Column(name = "id")
private Long uuid;
#Column(name = "name")
private String name;
#Column(name = "type")
private String type;
}
SecondData entity:
#Data
#Entity
#Table(schema = "someschema", name = "seconddata")
public class SecondData {
#Id
#Column(name = "id")
private Long uuid;
#Column(name = "type")
private String type;
#Column(name = "value")
private String value;
}
and DataService
#Service
public class DataService {
private DataRepository dataRepository;
public DataService(DataRepository dataRepository){
this.dataRepository = dataRepository;
}
public void getBothFirstAndSecondData() {
List<FirstData> firstDataSet = dataRepository.findAll();
}
}
I need to get data from both tables, but don't want to modify table structure, make jointable or add foreign keys. Also, i don't want to add another repository write code arount second data set. I need just to have a "value" from second data set at first data set result. What is the simpliest approach for solving such data retrieveing?
sql query the above problem can be:
select value from seconddata where id IN (select id from firstdata)
I've got 2 tables vehicle and vehicle_image. The vehicle table contains all master data of the vehicles and the vehicle_image table contains the meta information of the images and the Base64 encoded string of the image. On vehicle may have 0 or more images.
Now when I query the vehicle object I'd like the object to contain the information from the vehicle_image table.
I'm pretty new to JPA and the examples I could find always seem to read only one value from another table, not a list.
What would be the simplest way of adding an attribute to the vehicle object that contains the image data?
#Entity
#XmlRootElement(name = "vehicle")
public class Vehicle {
#Id
private String vin;
private String commission;
#Column(name="swiss_type_number")
private String swissTypeNumber;
#Column(name="sale_type")
private String saleType;
#Column(name="exterior_color")
private String exteriorColor;
#Column(name="interior_color")
private String interiorColor;
private String remarks;
#Column(name="additional_title")
private String additionalTitle;
#Column(name="added_value_description")
private String addedValueDescription;
#Column(name="first_registration")
private String firstRegistration;
private String guaranty;
#Column(name="last_inspection")
private String lastInspection;
private int dealer;
private int mileage;
private int price;
private int seats;
#Column(name="model_year")
private int modelYear;
#Column(name="car_damaged_in_accident")
private boolean carDamagedInAccident;
private boolean imported;
// List of images
List<VehicleImage> vehicleImages; // Something like this would be nice
}
JPA supports associations between entities. The one you need is #OneToMany
If your vehicle_image' table contains columnvehicle_idyou will need following mapping inVehicle` class:
#Entity
#Table(name = "VEHICLE")
#XmlRootElement(name = "vehicle")
public class Vehicle {
// other fields here
#OneToMany(mappedBy = "vehicle")
Set<VehicleImage> vehicleImages;
}
And also this in VehicleImage
#Entity
#Table(name = "VEHICLE_IMAGE")
public class VehicleImage{
// other fields here
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinColumn(name="VEHICLE_ID", referencedColumnName = "ID")
Vehicle vehicle;
}
Also you should better use Set for collections mapping in JPA, but it is a different topic
What you have here is a one-to-may relationship, you can use:
#OneToMany
List<VehicleImage> vehicleImages;
Refere to the javadoc for the attributes it may take and add them according to your table definitions.
Here you can find some examples of how to use it.
I try to add compostite Index in enttiy but not work
following error got :
[error] application - message= Unexpected database state: BTree 49 is not found, cause= [ObjectDB 2.5.4] javax.persistence.PersistenceException
Unexpected database state: BTree 49 is not found (error 147
Model Classes:
#Entity
#Table(name = Customer.TABLE_NAME)
#javax.jdo.annotations.Index(members=
{"addresses.firstName,addresses.lastName,addresses.company"})
public class Customer extends ObjectDBBaseModel<Customer> {
List<Address> addresses;
}
#Entity
#Table(name = Address.TABLE_NAME)
public class Address<T extends ObjectDBBaseModel> extends ObjectDBBaseModel<T> {
public static final String TABLE_NAME = "address";
public static final Address NULL = new Address();
public static ODBFinder<Address> find = new ODBFinder<>(Address.class, NULL);
public Address() {
super((Class<T>) Address.class);
}
#Column(name = "address_type_enum")
#Enumerated(EnumType.STRING)
private AddressTypeEnum addressTypeEnum;
#Column(name = "gender_enum")
#Enumerated(EnumType.STRING)
private GenderEnum genderEnum;
private String title;
private String firstName;
private String lastName;
private String company;
private String street1;
}
The index definition is invalid, since a multi path index is limited to one entity class (and additional embeddable classes) but cannot spread over multiple entity classes.
In this case you should use two separate indexes:
Simple index on the collection of addresses.
Composite index on the three fields in Address.
ObjectDB will maintain each index separately but will join them together in relevant queries.