#SqlResultSetMapping unknown columns referenced in entity mapping - spring-boot

I am trying to map only certain fields to the entity object using a native SQL query :
#NamedNativeQuery(name = "CustomerEntity.findOnlyNameAndPhoneFromCustomer", query = "select customer_name, customer_email from customer",
resultSetMapping = "nativeMapping")
#SqlResultSetMappings(value = {
#SqlResultSetMapping(name = "nativeMapping",
entities = {
#EntityResult(
entityClass = CustomerEntity.class,
fields = {
#FieldResult(name = "name", column = "customer_name"),
#FieldResult(name = "email", column = "customer_email")
}
)})})
#Entity
class CustomerEntity {
//getter and setter fields
#Column(name="customer_name")
private String name;
#Column(name="customer_email")
private String email;
#Column(name="address")
private String adddress;
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Access(AccessType.PROPERTY)
#Column(columnDefinition = "VARCHAR(36)", name = "customer_guid")
#Type(type = "uuid-char")
private UUID guid;
#Embedded
private AuditFields audit;
}
Repository:
#Query(nativeQuery = true)
List<CustomerEntity> findOnlyNameAndPhoneFromCustomer();
I am not trying to map all the fields that are present in the customer table to CustomerEntity, I am only projecting certain fields.
This is giving me errors like:
17:44:37.841 [ERROR] o.h.e.j.s.SqlExceptionHelper - The column name address2_6_0_ is not valid.
There is no column called address2_6_0_ in my table, but there is a column called address, why is the address column being renamed and referenced here ?
I am only referencing customer_name and customer_email.
What is going on ?
Thanks.

entities is for "mapping to entities".
#EntityResult:
If this annotation is used, the SQL statement should select all of the columns that are mapped to the entity object.
Therefore, you should use classes and #ConstructorResult for "mapping to DTOs".
#NamedNativeQuery(name = "CustomerEntity.findOnlyNameAndPhoneFromCustomer",
query = "select customer_name, customer_email from customer",
resultSetMapping = "nativeMapping")
#SqlResultSetMappings(value = {
#SqlResultSetMapping(name = "nativeMapping",
classes = #ConstructorResult(columns = { #ColumnResult(name = "customer_name"), #ColumnResult(name = "customer_email") },
targetClass = CustomerEntity.class)) })
#Entity
public class CustomerEntity {
public CustomerEntity() {
}
public CustomerEntity(String name, String email) {
this.name = name;
this.email = email;
}
...
}

Related

JPA Native Query. Cannot select specific columns

I'm working on a Spring Boot project using JPA to connect to my DB. I wan to make a native query to select some specific fields but it doesn't allow me to do. For example, I want to get only id, firstName, lastName and phoneNumber of a customer But it will throws me error like,
The column name current_access_token was not found in this ResultSet.
Here is my query code in the JPA repository,
#Query(value = "SELECT c.id, c.phone_number, c.firstname, c.lastname FROM tbl_customers c JOIN tbl_subscriptions s ON c.id = s.customer_id WHERE s.role = 'member' AND s.deleted_at IS NULL", nativeQuery = true)
List<Customer> findMemberByRole(String role);
Here is my Cutomer.java
#Getter
#Setter
#Accessors(chain=true)
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "tbl_customers")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(unique = true)
private Long id;
#Column(nullable = false, name = "phone_number")
private String phoneNumber;
private String firstname;
private String lastname;
#Column(name = "current_access_token")
private String currentAccessToken;
#Column(name = "consent_accepted")
private Boolean consentAccepted;
...
...
}
How can I avoid or ignore unwanted columns? Thanks a lot for helps.
If you really want to return only 4 columns from the customer table, then the signature you want to use here is List<Object[]>:
#Query(value = "SELECT c.id, c.phone_number, c.firstname, c.lastname FROM tbl_customers c JOIN tbl_subscriptions s ON c.id = s.customer_id WHERE s.role = 'member' AND s.deleted_at IS NULL", nativeQuery = true)
List<Object[]> findMemberByRole(String role);
Then, when accessing your result set, you would use something like:
List<Object[]> resultSet = findMemberByRole("admin");
for (Object[] rs : resultSet) {
Long id = (Long) rs[0];
String phoneNumber = (String) rs[1];
String firstName = (String) rs[2];
String lastName = (String) rs[3];
}

Spring boot JPA - Insert or update a list of entities

I have a repo with a unique constraint on 2 fields, connection_id and token_type:
#Entity
#Table(
name = "business_api_token",
schema = "public",
uniqueConstraints = {
#UniqueConstraint(
name = "business_api_token_unique_connection_id_and_token_type",
columnNames = {"connection_id", "token_type"}
)
}
)
public class BusinessApiToken {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(
name = "connection_id",
nullable = false,
foreignKey = #ForeignKey(
name = "fk_business_api_token_connection_id"
)
)
private AccountingConnection connection;
#Column(name = "token_type")
#Enumerated(EnumType.STRING)
private ApiTokenType tokenType;
#Column(name = "token_value")
private String tokenValue;
...
}
I saw some posts saying add a custom query, something like this:
#Modifying
#Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);
But how would I do this for a list? I was doing this:
businessApiTokenRepository.saveAll(tokens)
Which gives an error.
The tokens are created elsewhere without knowledge of existing ones, I can do another query to check first but that seems inefficient, and I have to do this all over.
Thanks

JPA Failing to produce a proper SQL query when a parameter has a composite primary key

Today I came across a weird bug while trying to test a JPA update query and I'm wondering if this a SpringBoot bug.
I have the following entities
An Entry entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class Entry {
#Id
private String id;
#ManyToOne(targetEntity = User.class)
#JoinColumn(referencedColumnName = "username")
#NotNull
private final User username;
#Enumerated(EnumType.STRING)
#NotNull
private Type type;
#ManyToOne(targetEntity = Category.class)
#JoinColumns({#JoinColumn(referencedColumnName = "name"),#JoinColumn(referencedColumnName = "type"),#JoinColumn(referencedColumnName = "username")})
#NotNull
private Category category;
#Size(max = 45)
#NotBlank
private String description;
#NotNull
private Double amount;
#NotNull
private final Date createdAt;
private Timestamp lastUpdate;
#NotNull
private Boolean isDeleted;
public enum Type{
Income,Expense
}
}
A Category entity with a composite key
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
#Setter
#Getter
#EqualsAndHashCode(of = {"id"})
#ToString(of = {"id"})
public class Category {
#EmbeddedId
private CategoryId id;
private final Timestamp createdAt = Timestamp.from(Instant.now());
#ManyToOne(targetEntity = User.class)
#JoinColumn(referencedColumnName = "username")
private final User user;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "category")
private List<Entry> entries;
public Category(String name, Type type, User user){
this.id = new CategoryId(name,type,user.getUsername());
this.user = user;
}
}
A CategoryID that is the embeddable composite key of the Category entity
#Data
#Embeddable
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EqualsAndHashCode(of = {"name","type","username"})
public class CategoryId implements Serializable {
private String name;
#Enumerated(EnumType.STRING)
private Type type;
private String username;
}
The following repository
#Repository
public interface EntryRepository extends JpaRepository<Entry, String> {
Optional<Entry> findEntryById(String id);
#Modifying(clearAutomatically = true, flushAutomatically = true)
#Query(value = "UPDATE Entry e SET e.username = :username, e.type = :type, e.category = :category, e.description = :description, e.amount = :amount, e.createdAt = :date, e.lastUpdate = :lastUpdate, e.isDeleted = :isDeleted WHERE e.id = :id")
void update(#Param("id") String id,
#Param("username") User username,
#Param("type") Entry.Type type,
#Param("category") Category category,
#Param("description") String description,
#Param("amount") Double amount,
#Param("date") Date date,
#Param("lastUpdate") Timestamp lastUpdate,
#Param("isDeleted") Boolean isDeleted);
}
And finally the following Unit Test
#Test
void update() {
//given
User testUser = userRepository.save(new User("testUser#test.com","000000000000000000000000000000000000000000000000000000000000"));
Category testCategory = categoryRepository.save(new Category("Test Category", Entry.Type.Income,testUser));
Entry testEntry = new Entry("testEntry",testUser, Entry.Type.Income,
testCategory, "test",
0.0, new Date(343), from(now()), false);
System.out.println(testCategory);
entryRepositoryUnderTest.save(testEntry);
//when
entryRepositoryUnderTest.update("testEntry",testUser,Expense,testCategory,"testUpdated",1.0,new Date(346), from(now()),true);
Optional<Entry> actual = entryRepositoryUnderTest.findEntryById("testEntry");
System.out.println(actual.get().getCategory());
//then
assertThat(actual.get().getUsername()).isEqualTo(testUser);
assertThat(actual.get().getType()).isEqualTo(Expense);
assertThat(actual.get().getCategory()).isEqualTo(testCategory);
assertThat(actual.get().getDescription()).isEqualTo("testUpdated");
assertThat(actual.get().getAmount()).isEqualTo(1.0);
assertThat(actual.get().getIsDeleted()).isEqualTo(true);
}
When I run the test it fails and I get the following error message:
could not execute update query; SQL [update entry set username_username=?, type=?,category_name=?=category_type=?, description=?, amount=?, created_at=?, last_update=?, is_deleted=? where id=?]; nested exception is org.hibernate.exception.DataException: could not execute update query
As you can see here when SpringBoot is trying to produce a SQL query statement from my #Query parameter it can not properly extract the Category field from the parameters and inject it's composite embeddable key into the SQL statement. It has no problem extracting the User parameter because the User is an entity with an id that is not composite.
Is this a SpringBoot bug or am I missing something?
EDIT:
This is the structure of the database

Mapping objects from Room Persistence

I have classes - Subject.java
#Entity(tableName = Constants.SUBJECT_ENTITY)
public class Subject {
#PrimaryKey
private int id;
#ColumnInfo(name = "name")
private String name;
...
}
Grade.java
#Entity(tableName = Constants.GRADE_ENTITY,
foreignKeys = #ForeignKey(
entity = Subject.class,
onDelete = CASCADE,
parentColumns = "id",
childColumns = "subjectId"))
public class Grade implements Serializable {
#PrimaryKey
private int id;
#ColumnInfo(name = "semester")
private int semester;
#ColumnInfo(name = "subjectId")
private int subjectId;
...
}
SubjectAndAllGrades.java
public class SubjectAndAllGrades {
public int id;
public String name;
#Relation(parentColumn = "id", entityColumn = "subjectId")
public List<Grade> oceny;
...
}
I have performed some query in my SubjectDAO
#Query("SELECT distinct s.id as id, s.name as name " +
"FROM subject s, grade g " +
"WHERE s.id = g.subjectId " +
"AND g.semester = :semester ")
Flowable<List<SubjectAndAllGrades>> getSubjectAndAllItsGradesForSemester(int semester);
In this query as you can probably guess Im trying to select all Subjects with its grades with specific semester. However results of this query return list of SubjectAndAllGrades objects connected by subjectId key, but with no respect to the condition
g.semester = :semester
How could I achive this query?

Querying composite table in Hibernate

I am working on a Spring-MVC application where I have a many-to-many relationship in which I have to query in 2 tables to get the values I require. I will explain in more detail.
I have 2 tables GroupAccount, GroupMembers with many-to-many
relationship. Now there is a junction table called membertable where
id from GroupMembers and GroupAccount is stored.
This is what I am looking for :
I pass a groupAccounId and username as parameters. Now, in the
GroupMembers table, there is a username stored. In groupAccount,
there is groupAccountId is stored.
Now in the memberjunction, I have composite key
memberid,GroupAccountId, I would like the member id for the username
which has a matching groupAccountId I submit.
Below is the SQL code and Spring-mvc code to understand more better.
CREATE TABLE public.groupaccount (
groupid NUMERIC NOT NULL,
groupname VARCHAR,
groupaccountstatus BOOLEAN DEFAULT false NOT NULL,
adminusername VARCHAR,
CONSTRAINT groupid PRIMARY KEY (groupid)
);
CREATE TABLE public.groupmembers (
memberid INTEGER NOT NULL,
musername VARCHAR
CONSTRAINT memberid PRIMARY KEY (memberid)
);
CREATE TABLE public.memberjunction (
memberid INTEGER NOT NULL,
groupid NUMERIC NOT NULL,
CONSTRAINT membergroupid PRIMARY KEY (memberid, groupid)
);
GroupMembersDAOImpl :#
#Override
public List<Integer> returnMemberIdWithMatchingUsername(String memberUsername) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("From GroupMembers as " +
"n where n.memberUsername=:memberUsername");
query.setParameter("memberUsername",memberUsername);
List<GroupMembers> memberList = query.list();
List<Integer> memberIdList = new ArrayList<>();
for(GroupMembers members :memberList){
memberIdList.add(members.getMemberid());
}
return memberIdList;
}
GroupAccount model :
#Entity
#Table(name="groupaccount")
public class GroupAccount {
#Id
#Column(name="groupid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupaccount_seq_gen")
#SequenceGenerator(name = "groupaccount_seq_gen",sequenceName = "groupaccount_seq")
private Long groupId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "memberjunction", joinColumns = {#JoinColumn(name = "groupid")},
inverseJoinColumns = {#JoinColumn(name = "memberid")})
private Set<GroupMembers> groupMembersSet = new HashSet<>();
public void setGroupMembersSet(Set<GroupMembers> groupMembersSet){
this.groupMembersSet = groupMembersSet;
}
}
GroupMembers model class :
#Entity
#Table(name="groupmembers")
public class GroupMembers {
#Id
#Column(name="memberid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupmembers_seq_gen")
#SequenceGenerator(name = "groupmembers_seq_gen",sequenceName = "groupmembers_seq")
private int memberid;
#ManyToMany(mappedBy = "groupMembersSet")
private Set<GroupAccount> groupAccounts = new HashSet<>();
public void setGroupAccounts(Set<GroupAccount> groupAccounts){
this.groupAccounts = groupAccounts;
}
public Set<GroupAccount> getGroupAccounts(){
return this.groupAccounts;
}
}
Query I am using :
#Override
public int getMemberIdForCanvas(String memberUsername, Long groupId) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("select distinct m.memberId from GroupMembers m\n" +
"join m.groupAccounts a\n" +
"where a.memberUsername = :userName and m.groupId=:groupId");
query.setParameter(memberUsername,"memberUsername");
query.setParameter(String.valueOf(groupId),"groupId");
int memberid = (Integer)query.uniqueResult();
return memberid;
}
Any help would be nice. Thanks a lot.
Here's the documentation for joins and HQL. Please read it.
The query is as simple as
select distinct m.memberId from GroupMembers m
join m.groupAccounts a
where a.memberUsername = :userName
Please also fix your naming. A GroupMembers instance is a single group member. So the class should be named GroupMember, without s. Repeating the name of the class in the fields of this class is also redundant: member.getId() is more readable and less verbose than member.getMemberId(). Same for the other fields.

Resources