Is there a way to group by a collection property? For example,
public class Merchandise {
id,
name
}
public class Attribute {
id,
name,
value,
#ManyToOne
MerchandiseCost merchandiseCost;
}
public class MerchandiseCost {
Merchandise merchandise,
List<Attribute> attributes,
BigDecimal cost,
}
Search MerchandiseCost group by merchandise and attributes.
select merchandise, attributes, sum(cost) from MerchandiseCost group by merchandise, attributes.
Will this be going to work?
EDIT:
If not, how to build a query to get results as following using CriteriaQuery API:
Merchandise Attributes SUM(COST)
-----------------------------------------------------------
Cloth size:L, color:RED 10000
Cloth size:M, color:WHITE 20000
Computer Memory:4G 80000
Computer Memory:16G 90000
You can not group by a collection and cannot select multi-valued field in the Select clause.
Merchandise.class
#Data
#Embeddable
#NoArgsConstructor
#EqualsAndHashCode
public class Merchandise {
private String name;
}
Attribute.class
#Data
#Embeddable
#EqualsAndHashCode
public class Attribute {
private int id;
private String name;
private String value;
private MerchandiseCost merchandiseCost;
#ManyToOne
public MerchandiseCost getMerchandiseCost() {
return merchandiseCost;
}
}
MerchandiseCost.class
#Data
#Entity
#EqualsAndHashCode
public class MerchandiseCost extends ABaseEntity {
private Merchandise merchandise;
private List<Attribute> attributes;
private BigDecimal cost;
#Embedded
public Merchandise getMerchandise() {
return merchandise;
}
public void setMerchandise(Merchandise merchandise) {
this.merchandise = merchandise;
}
#ElementCollection
#CollectionTable(name = "MERCHANDISE_ATTRIBUTE", joinColumns = #JoinColumn(name = "MERCHANDISE_ID"))
public List<Attribute> getAttributes() {
return attributes;
}
}
MerchandiseResult.class
#Data
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class MerchandiseResult {
private Merchandise merchandise;
private Attribute attribute;
private BigDecimal cost;
}
MerchandiseDao.class
#Stateless
public class MerchandiseDao {
#PersistenceContext(name = "tngo")
private EntityManager entityManager;
public void readCost(){
Query query = entityManager.createQuery("select NEW tngo.cert.training.model.MerchandiseResult(mc.merchandise, att, sum(mc.cost)) from MerchandiseCost mc join mc.attributes att group by mc.merchandise, att");
query.getResultList();
}
}
Related
This question is already phrased as an issue here: https://github.com/spring-projects/spring-data-jpa/issues/2369 but for lack of a reaction there I am copying the contents of that issue here, hoping that somebody might find what's wrong with my code or confirm that this could be a bug:
I've set up an example project here that showcases what seems to be a bug in Spring Data projections: https://github.com/joheb-mohemian/gs-accessing-data-jpa/tree/primary-key-join-column-projection-bug/complete
I have a Customer entity that has a OneToOne mapping to an Address entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address address;
//...
}
#Entity
public class Address {
#Id
#Column(name = "customer_id")
private Long id;
#OneToOne
#MapsId
#JoinColumn(name = "customer_id")
private Customer customer;
private String street;
//...
}
Then there are simple projection interfaces:
public interface CustomerProjection {
String getFirstName();
String getLastName();
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreet();
}
But when I try to fetch a projected entity from a repository method like this one:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
//...
<T> T findById(long id, Class<T> type);
}
, getAddress() on the projection will be null, whereas getAddress() when fetching the entity type is populated correctly. Of these two unit tests, only testEntityWithOneToOne()will be successful:
#BeforeEach
void setUpData() {
customer = new Customer("first", "last");
Address address = new Address(customer, "street");
customer.setAddress(address);
entityManager.persist(address);
entityManager.persist(customer);
}
#Test
void testEntityWithOneToOne() {
Customer customerEntity = customers.findById(customer.getId().longValue());
assertThat(customerEntity.getAddress()).isNotNull();
}
#Test
void testProjectionWithOneToOne() {
CustomerProjection customerProjection = customers.findById(customer.getId(), CustomerProjection.class);
assertThat(customerProjection.getAddress()).isNotNull();
}
What's the problem here?
I'm studying bidirectional mapping.
I mapped Team and Member with #OneToOne and #ManyToMany annotations.
#Entity
public class Team {
#Id #GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members=new ArrayList<Member>();
//omit getter, setter ,toString
}
#Entity
public class Member {
#Id
#GeneratedValue
private Long id;
#Column(name="USERNAME")
private String name;
#ManyToOne
#JoinColumn(name="TEAM_ID")
private Team team;
#Enumerated(EnumType.STRING)
private Status status;
//omit getter, setter , toString
}
main method
public static void main(String args[]){
//...
Team team= new Team();
team.setName("RedTeam");
em.persist(team);
Member member= new Member();
member.setName("me");
member.setStatus(Status.ADMIN);
member.setTeam(team);
em.persist(member);
Member findmember= em.find(Member.class, member.getId());
Team findTeam= findmember.getTeam();
System.out.println("members: "+findTeam.getMembers());
//...
}
results:
members: []
I wonder why "members" were not added to the "members field" of "Team" in the code above.
Thank you in advance.
These are the minimal changes to make it to work
#Entity
public class Team {
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private #Id Long id;
private String name;
#OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Member> members = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String pName) {
name = pName;
}
public void addMember(Member m) {
getMembers().add(m);
m.setTeam(this);
}
public void removeMember(Member m) {
getMembers().remove(m);
m.setTeam(null);
}
public List<Member> getMembers() {
return members;
}
}
Team team = new Team();
team.setName("RedTeam");
// em.persist(team);
Member member = new Member();
member.setName("" + new Random().nextInt(999999));
team.addMember(member);
em.persist(team);
Member findmember = em.find(Member.class, member.getId());
Team findTeam = findmember.getTeam();
System.out.println("Members: " + findTeam.getMembers());
How to give composite unique keys ( address1 and address2) as Map key. Like #MapKey(name = "address1", name = "address2") instead of single unique key #MapKey(name = "address1").
#Entity
public class Person {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "person")
#MapKey(name = "address1")
public Map<String, Address> getAddressMap() {
return addressMap;
}
}
#Entity
public class Address {
private Integer id;
private String address1;
private String address2;
private Person person;
}
Use Embeddable type.
Create an embeddable class (i.e. PersonAddress) to encapsulate your address1 and address2 properties. Then use that embeddable class as a Map key.
#Entity
public class Person {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "person")
public Map<PersonAddress, Address> getAddressMap() {
return addressMap;
}
}
#Entity
public class Address {
private Integer id;
#Embedded
private PersonAddress;
#ManyToOne
private Person person;
}
#Embeddable
public class PersonAddress {
private String address1;
private String address2;
}
I want to get the branch object in which leaf(using it's ID) belongs to
What is the right approach to get the branch given that I only have the leaf ID? I thought of looping through all the branches in the db and get the one which contain the leaf ID which looks bad
#Entity
public class Branch {
#Id
#GeneratedValue
Long id;
#OneToMany
#JoinColumn(name = "branch_id")
private List<Leaf> leaves
}
#Entity
public class Leaf {
#Id
#GeneratedValue
Long id;
private String name;
}
#Service
public class BranchService {
private final BranchRepository branchRepository;
#Autowired
public BranchService(BranchRepository branchRepository) {
branchRepository = branchRepository;
}
public Tree getBranchByLeaf(Long leafId){
// ??
}
}
Try something like this:
public interface BranchRepository extends JpaRepository<Branch, Long> {
#Query("select b from Branch b join b.leaves l where l.id = ?1")
List<Branch> getByLeafId(Long leafId);
}
#Service
public class BranchService {
private final BranchRepository branchRepository;
#Autowired
public BranchService(BranchRepository branchRepository) {
branchRepository = branchRepository;
}
public List<Branch> getByLeafId(Long leafId){
return branchRepository.getByLeafId(Long leafId);
}
}
I have a situation where lazy-loading is not occurring when I want it to in a one-to-many.
Obviously, I am new to this. Why won't the SongComposers get fetched when I call s.getSongComposers()?
Here is my DAO class:
public class SongDAOImpl implements SongDAO {
#PersistenceContext
private EntityManager em;
#Override
#SuppressWarnings(value = { "unchecked" })
public List<Song> getAllSongsOnAlbum(int albumID) {
Query q = em.createQuery("SELECT s FROM Song s WHERE s.album.albumID = :albumid ORDER BY s.songName");
q.setParameter("albumid", albumID);
List<Song> list = (List<Song>) q.getResultList();
for (Song s: list) {
s.getSongComposers();
}
return list;
}
}
Here is my Song entity:
#Entity
#Table(name="songs")
public class Song {
#Id
#GeneratedValue
#Column(name="SongID")
private int songID;
#Column(name="SongName")
private String songName;
#Column(name="Length")
private int length;
#ManyToOne
#JoinColumn(name="AlbumID", referencedColumnName="AlbumID")
private Album album;
#OneToMany(mappedBy="song")
private List<SongComposer> songComposers;
.... all getters/setters ......
}
Here is my SongComposer entity:
#Entity
#Table(name="song_composer")
public class SongComposer {
#Id
#GeneratedValue
#Column(name="SongComposerID")
private int songComposerID;
#ManyToOne
#JoinColumn(name="SongID", referencedColumnName="SongID")
private Song song;
#ManyToOne
#JoinColumn(name="ComposerID", referencedColumnName="ComposerID")
private Composer composer;
..... getters/setters .......
}
You have to do it explicitly. Try this query:
SELECT DISTINCT s FROM Song s JOIN FETCH s.songComposers WHERE s.album.albumID = :albumid ORDER BY s.songName"