I'm using spring data and I made this query:
#Query("SELECT DISTINCT u.pk.fleet.fleetName FROM FleetHasUser u WHERE u.pk.user.username = ?1")
List<FleetName> allFleetNameForUser(String username);
The aim of this query is find all FleetName for a specific user.
This is the part of database schema interested in:
The FleetHasUser class:
#Entity
#Table(name = "fleet_has_user", catalog = "dart")
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id_username")),
#AssociationOverride(name = "pk.fleet",
joinColumns = #JoinColumn(name = "id_fleet")) })
public class FleetHasUser implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private FleetHasUserKeys pk = new FleetHasUserKeys();
#EmbeddedId
public FleetHasUserKeys getPk() {
return pk;
}
the FleetHasUserKeys class:
#Embeddable
public class FleetHasUserKeys implements Serializable {
private static final long serialVersionUID = 1L;
protected User user;
protected Fleet fleet;
Fleet class:
#Entity
#Table(name = "fleet", catalog = "dart")
public class Fleet implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idFleet;
private Ecu ecu;
private String application;
private Cubic cubic;
private Integer power;
private String euroClass;
private String engineType;
private Traction traction;
private String transmission;
private String note;
private FleetName fleetName;
#JsonIgnore
private Set<Car> cars = new HashSet<Car>(0);
#JsonIgnore
private Set<FleetHasUser> fleetHasUsers = new HashSet<FleetHasUser>(0);
As you can see FleetName is a class. I tried without distinct and the list had duplicated elements, so before create a subquery I inserted distinct only for test and it worked. I didn't find information on google, but is it possible to use distinct on a object? How does it work(check the primary key fields)?
Maybe may I even create a Spring data query like findDistinctPkFleetFleetNameByPkUserUsername?
Thanks
As far as i know, the distinct is being applied on the query not on the mapping phase, as you can see #Query doc the annotation only executes jpql queries so a native distinct.
As you say other option to execute a distinct is
Spring data Distinct
You cannot apply "Distinct" to objects, but you can map then on Set's to avoid duplicated, but the better approach is filter properly the data on the database.
Related
I have these Objects:
#Data
#Entity
#Table
#EqualsAndHashCode(callSuper = true)
public class User extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -55089179131569489L;
private String username;
private String email;
private boolean admin;
private String name;
private String surname;
#OneToMany(mappedBy = "owner")
private List<Ad> ads;
}
and
#Entity
#Table
#Data
#EqualsAndHashCode(callSuper = true)
public class Ad extends AbstractEntity implements Serializable {
private static final long serialVersionUID = -4590938091334150254L;
private String name;
private String description;
private double price;
#Enumerated(EnumType.STRING)
private Category category;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "OWNER_ID")
private User owner;
}
When I try to execute a POST with an object of type Ad.class with inside an existing object of type User.class (already in the Database) the service saves only the Ad object and the join column "OWNER_ID" remains empty.
I think that the mapping is correct. Could you help me to figure out the problem?
This is my Repository:
#Repository
#Transactional(readOnly = true)
public interface AdRepository extends PagingAndSortingRepository<Ad, String>
{}
and this is my RestRepository
#RepositoryRestResource(collectionResourceRel = "ad", path = "ad")
public interface AdRestRepository extends PagingAndSortingRepository<Ad, String> {}
If I step back a little and generalize your problem,
You are trying to POST a sub resource and expect both actions of
making a new resource (Ad)
making association with the owner (User)
to be happened with a single call.
But unfortunately spring-data-rest does not support such a behavior. You need 2 calls to do this.
One to make the resource (Ad) => POST to /ads with actual payload
Second to make the association => POST to users/{ownerId} with the hateoas link of the resource created by the first call.
Take a look at this section of official documentation.
The entity model and repository is given below.
Channel.java
public class Channel extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "channel_name")
private String channelName;
#Column(name = "channel_type")
private Integer channelType;
#Column(name = "seq_id")
private Integer seqId;
#Column(name = "channel_device_key")
private String channeldeviceKey;
}
UserRoomChannel.java
public class UserRoomChannel extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne
#JoinColumn(name = "user_house_id")
private UserHouse userHouse;
#OneToOne
#JoinColumn(name = "room_id")
private Room room;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<Channel> channels;
}
UserRoomChannelReposirtory.java
public interface UserRoomChannelRepository extends JpaRepository<UserRoomChannel, Long> {
#Query(value = "DELETE FROM user_room_channel_channels WHERE channels_id=?1", nativeQuery = true)
void deleteUserRoomChannelChannels(Long id);
}
I can save the data successfully. When data is saved through this a third table named user_room_channel_channels is created.
EX:
user_room_channel_id channels_id
1 1
1 2
But When I tried to delete with channels_id it give me the error
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:.....
The native query what I write it execute from the command line.
But using JPA I can't do that.
Any help or any suggestion for resolving this issue?
A collection with cascade="all-delete-orphan" was no longer referenced
by the owning entity instance
is because before you delete your channels(and its children), you somehow do the following:
you load your UserRoomChannel along with its Channels children in the collection from the Database.
somewhere in your code you change the reference of the children collection : myUserRoomChannel.setChannels(newChannelCollections) or myUserRoomChannel.channels =new ChannelCollections();
and you try to delete the user with your repositorisory.
Hibernate who remembered having set the children collection with reference A to the User can find the collection anymore, because User.channels is now User.channels == B (with B being a new reference to your collection).
How to fix it:
just find the place where you are replacing your children collections and instead of:
myUserRoomChannel.setChannels(newChannelCollections), or
myUserRoomChannel.channels =new ChannelCollections(),
just do
myUserRoomChannel.getChannels().add/delete/clearYourChannels()
I just have done the below step and it works perfectly.
Reomve:
myUserRoomChannel.setChannels(channels)
Add
myUserRoomChannel.getChannels().removeAll(channels) and then
userRoomChannelRepository.save(myUserRoomChannel)
I am using Spring Data JPA + Hibernate for a webapp. For a particular domain model A, we have a 1-to-many association in another domain B. Such that A will have a Set getB() and B will have A getA().
While querying for a A graph, I see hibernate is using 1+n queries. A single outer join query for fetching the A graph, but then 'n' queries for setting A in each B.
Am I missing any pattern here? Since all the childs have the same parent, is not somehow possible to avoid these 'n' queries?
#MappedSuperclass
#Data
public abstract class Batch implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "batch_id", referencedColumnName = "batch_id")
protected BatchID batchId;
}
/*
//The parent class in a simplified form
*/
#Entity
#Table(name = "DRYRUN")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class DryrunBatch extends Batch {
/**
*
*/
private static final long serialVersionUID = -1596595930859735318L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter#Setter
protected Long id;
public DryrunTNStatus newTNStatus()
{
final DryrunTNStatus tn = new DryrunTNStatus();
tn.setBatch(this);
getTnStatus().add(tn);
return tn;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "batch")
#Getter#Setter
private Set tnStatus = new HashSet();
}
//The child class in a simplified form
#Entity
#Table(name = "DRYRUN_TN_STATUS")
#Data
public class DryrunTNStatus implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4388406636444350023L;
public DryrunTNStatus(String accountNo, String telNo) {
super();
this.accountNo = accountNo;
this.telNo = telNo;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BATCH_ID", referencedColumnName = "BATCH_ID")
private DryrunBatch batch;
public DryrunTNStatus()
{
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
}
The code to fetch the object graph using JpaRepository. Using Spring JPA support to enforce an outer join. I preferred this over Hibernate's #Fetch annotation.
DryrunBatch drBatch = drBatchRepo.findOne(new Specification() {
#Override
public Predicate toPredicate(Root root, CriteriaQuery query,
CriteriaBuilder cb) {
query.distinct(true);
root.fetch("tnStatus", JoinType.LEFT);
return cb.equal(root.get("batchId").get("id"),
batch.getId());
}
});
And finally the hibernate queries from log. I am running a junit that fetches a parent with 10 childs from DB.
//this query can fetch data for the complete graph??
Hibernate: select distinct dryrunbatc0_.id as id1_6_0_, tnstatus1_.id as id1_9_1_[etc..] from dryrun dryrunbatc0_ left outer join dryrun_tn_status tnstatus1_ on dryrunbatc0_.batch_id=tnstatus1_.batch_id where dryrunbatc0_.batch_id=15
//and then 10 queries like
Hibernate: select dryrunbatc0_.id as id1_6_3_, [etc..] from dryrun dryrunbatc0_ left outer join batch_id batchid1_ on dryrunbatc0_.batch_id=batchid1_.batch_id inner join users user2_ on dryrunbatc0_.created_by=user2_.login_id left outer join dryrun_tn_status tnstatus3_ on dryrunbatc0_.batch_id=tnstatus3_.batch_id where dryrunbatc0_.batch_id=?
You've encountered the famous N+1 problem with lazy loading. There is no JPA standard way to tackle this, however, every JPA provider provides means to turn on "Batch fetching", which will load all lazy references at once instead loading each in a single SQL query.
Here is information on how to turn it on in hibernate.
Here is an article with explanation of how batch fetching works and examples using eclipselink.
I'm having an issue performing a custom query through the use of a spring data jpa repository.
I have a repository class implementing JPARepository<>. Everything works as expected for all of the built-in CRUD queries along with some custom queries, but doing qualification among inner collections isn't working and is returning back a full result set as though the qualification of the collection did not exist.
For example, here is a query:
public interface MessageRepository extends JpaRepository<Message, Integer> {
#Query("SELECT a FROM Message a, Message_Topic b WHERE a.systemNm = :theSystem AND a. applicationNm = :theApplication AND b.topicNm = :theTopicName AND a.insertTs BETWEEN :theStartDate AND :theEndDate AND a.expirationDt > CURRENT_TIMESTAMP")
List<Message> findMessagesByTopic(#Param("theSystem") String theSystem,
#Param("theApplication") String theApplication,
#Param("theTopicName") String theTopicName,
#Param("theStartDate") Date theStartDate,
#Param("theEndDate") Date theEndDate);
With the following JPA entities:
Message:
#Entity
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="message_id")
private int messageId;
#Column(name="application_nm")
private String applicationNm;
#Column(name="execution_instance_txt")
private String executionInstanceTxt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="expiration_dt")
private Date expirationDt;
#Column(name="grouping_des")
private String groupingDes;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="insert_ts")
private Date insertTs;
#Column(name="message_detail_txt")
private String messageDetailTxt;
#Column(name="message_summary_txt")
private String messageSummaryTxt;
#Column(name="severity_des")
private String severityDes;
#Column(name="system_nm")
private String systemNm;
//uni-directional many-to-one association to Message_Topic
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="message_id", referencedColumnName="message_id")
private Set<Message_Topic> messageTopics;
Message_Topic:
#Entity
public class Message_Topic implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="message_topic_id")
private int messageTopicId;
#Column(name="message_id", insertable=false, updatable=false)
private int messageId;
#Column(name="topic_nm")
private String topicNm;
#Column(name="topic_value_txt")
private String topicValueTxt;
This is your query:
SELECT a FROM Message a, Message_Topic b WHERE a.systemNm = :theSystem AND a. applicationNm = :theApplication AND b.topicNm = :theTopicName AND a.insertTs BETWEEN :theStartDate AND :theEndDate AND a.expirationDt > CURRENT_TIMESTAMP
Where are Message and Message_Topic joined?, If you transform this query to a native query, is possible you can detect the fault.
In hibernate, for example I have two object which has relation. The object is like this
First object : Customer
#Entity
#Table(name = "customer", catalog = "test")
public class Customer implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private Set<CustomerController> customerControllers = new HashSet<CustomerController>(0);
public Customer() {
}
//getter & setter
}
Second Object : CustomerController
#Entity
#Table(name = "customer_controller", catalog = "test")
public class CustomerController implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private Customer customer;
//constructor, getter & setter
}
I want to select the customer_controller of certain customer. I get it by two manner. First manner :
#Override
public List<CustomerController> customerController(int customerId){
Customer customer = (Customer) sessionFactory.getCurrentSession().get(Customer.class, customerId);
return customer.getCustomerControllers()
}
Second manner :
return (List<CustomerController>)sessionFactory.getCurrentSession().createQuery("SELECT O FROM CustomerController O WHERE O.customerId=:CONDITION")
.setParameter("CONDITION", customerId)
.list();
Which manner is the most efficient one? Why?
Thank you.
To ensure it is easier to "turn on" show SQL parameter and monitor it.
I suppose in first hibernate able to generate two SQL query with entity mapping.
In second case should be generated only one select query.
In case when we use FetchType.EAGER think Hibernate will map Customer and CustomerController entity. Hope Hibernate fetch only CustomerController using HQL. To ensure you should monitor Hibernate behavior.