Exception when selecting specific columns using Hibernate and Spring Data JPA - postgresql

I have a table that has a bytea column (named 'pdf') and I don't want to always select it, specially when I'm returning a list from the database, due to performance issues.
I use native queries with spring data inside the repository to solve these types of situations before (when I used eclipselink), but with Hibernate, if I don't write all the columns in the query, it throws an exception.
For test purposes, I'm trying to select only the id from the User and I still get the exception.
Example: "SELET user.id FROM user WHERE user.id = '1'"
It throws an exception saying that it did not find name in the ResultSet, if I put name in the SQL, it then says age was not found and so on, until I have to write all the columns in the SQL.
Thanks in advance for any help.
What I have tried already:
Updating/Downgrading Hibernate and Spring Data with no luck.
Creating a new entity with only the columns I need, works, but it's a messy solution for me.
Maybe the problem is the combination of the frameworks I use and the way I use them, if someone wants, I could try to upload my whole project structure.
My code:
Entity
#Entity
#Table(name = "user", schema = "portal")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Column(name = "pdf")
private byte[] pdf;
#Column(name = "name")
private String name;
#Column(name = "age")
private Integer age;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public byte[] getPdf() {
return pdf;
}
public void setPdf(byte[] pdf) {
this.pdf = pdf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Anexo)) {
return false;
}
Anexo other = (Anexo) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "br.gov.to.secad.portal.domain.User[ id=" + id + " ]";
}
}
Service
#Service
#Transactional(readOnly = true)
public class UserService implements Serializable {
private static final long serialVersionUID = 1L;
#Autowired
private IUserRepository userRepository;
public UserService() {
}
public User findOne() {
return userRepository.findOneSQL();
}
}
Repository
public interface IUserRepository extends JpaRepository<User, Serializable>, JpaSpecificationExecutor {
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
public User findOneSQL();
}
The exception:
org.postgresql.util.PSQLException: The column name name was not found in this ResultSet.
Solution
The solution is using an array of Object when I want to select anything less than what I've mapped on my Entity class, thats the limitation of Hibernate that I now understand.
So basically, the method will return Object[] and then I can iterate each position and instantiate a new entity of User with these values.
Example:
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
public Object[] findOneSQL();

I have faced the same problem, I know it is late but well there is a solution that I found elegant.
By the Spring documentation you can declare an interface and from here take the fields you want, in my case it has been something similar to this.
The interface to minimize the fields:
public interface CountryMinify {
String getName();
String getNameTranslation();
}
And my JpaRepository
public interface PlanetRepository extends JpaRepository<Planet, Long> {
#Query(value = "select p.name_country as name, p.name_country_translation as nameTranslation from vm_planet p where gid = ?1", nativeQuery = true)
CountryMinify findByCode(String codeCountry);
}
Keep in mind that the columns should be called the same as gos getter. For example: column name_country -> AS name and the getter of the interface is getName()

Try this
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
Integer findOneSQL();
Call the method like so
Integer user = userRepository.findOneSQL();
Edit 1 :
Since you are using native query you wont be able to use Projections which is a great way of accessing only certain entity fields. There is a JIRA ticket which is still under investigation.
Solution
Return List from your repository like so
#Query(value = "SELECT user.id, user.name FROM user WHERE user.id = '1'", nativeQuery = true)
List<Object[]> findOneSQL();
Iterate over the list of Objects and get your specific columns.
List<Object[]> userNative = userRepository.findOneSQL();
for (Object[] obj : userNative) {
System.out.println("User id : " + obj[0]);
System.out.println("User Name : " + obj[1]);
}

Related

How to convert Integer param which can be 'null' to '0' when selecting data with Spring Data JPA

Assume we have entity Animal. There are animals in DB with 'amount' = null, it's a valid case to save animal without the 'amount'.
Is there a way to convert field 'amount' to 0 in case it's null in query?
The simplest workaround seems to convert amount null to '0' earlier
when saving, but it's not allowed.
As another workaround we can do this mapping to '0' after fetching
it from the repository. When sorting by amount in asc order, null values will be at the beginning, in desc order they will be at the end. And after
converting to '0' everything will be at the right place. But it seems that can cause problems with pagination in future
What is the proper way to do it in Query?
Spring Data Jpa 2.2.9.RELEASE, Postgresql 42.2.16.
#Repository
public interface AnimalRepository extends JpaRepository<AnimalEntity, Long> {
#Query(value = "SELECT animal FROM AnimalEntity animal" +
" WHERE animal.ownerId = :ownerId" +
" and function('replace', upper(animal.name), '.', ' ') like function('replace', upper(concat('%', :name,'%')), '.', ' ') "
)
Page<AnimalEntity> findAllLikeNameAndOwnerSorted(String ownerId, String name, Pageable pageable);
}
#Entity
#Table(name = "animal")
public class AnimalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer amount;
private String name;
private String ownerId;
}
UPDATE
Also important to mention. The solution I suggested with replacing nulls with zero is incorrect, because of the different null ordering in Postgresql and HSQLDB.
But it will work in tests, if you're using HSQLDB.
Animal entities in DB test sample: [
Animal(name=Cat, amount=599999.99),
Animal(name=Dog, amount=null),
Animal(name=John, amount=5000)
]
Hsqldb amount desc query result:
[
Animal(name=Cat, amount=599999.99),
Animal(name=John, amount=5000),
Animal(name=Dog, amount=null)
]
Postgresql amount desc query result:
[
Animal(name=Dog, amount=null)
Animal(name=Cat, amount=599999.99),
Animal(name=John, amount=5000)
]
The JPA supports the COALESCE function. Thus you can set up the desired value via this function.
SELECT COALESCE(amount,0) AS desiredAmount FROM AnimalEntity animal
The code should look like this:
#Entity
#Table(name = "animal")
public class AnimalEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer amount;
public AnimalEntity() {
}
public AnimalEntity(Integer amount, String name) {
this.amount = amount;
this.name = name;
}
public Long getId() {
return id;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And the repository:
#Repository
public interface AnimalRepository extends JpaRepository<AnimalEntity, Long> {
#Query(
value = "SELECT animal.id AS id, COALESCE(animal.amount,0) AS amount, UPPER(animal.name) AS name FROM animal animal WHERE animal.name = :name",
nativeQuery = true)
Page<AnimalEntity> findAllLikeNameAndOwnerSorted(String name, Pageable pageable);
}
Also I have prepared the test:
#SpringBootTest
class AnimalRepositoryTest {
#Autowired
private AnimalRepository animalRepository;
#Test
void findAllLikeNameAndOwnerSorted() {
AnimalEntity animalEntity = new AnimalEntity(null, "dog");
animalRepository.save(animalEntity);
AnimalEntity animalEntity2 = new AnimalEntity(1, "CAT");
animalRepository.save(animalEntity2);
System.out.println(animalEntity2.getId());
Pageable sortedByName = PageRequest.of(0, 3, Sort.by("id"));
Page<AnimalEntity> animals = animalRepository.findAllLikeNameAndOwnerSorted("dog", sortedByName);
animals.forEach(System.out::println);
}
}
You can check the commit: https://gitlab.com/chlupnoha/meth/-/commit/76abbc67c33b2369231ee89e0946cffda0460ec9 - it is experiment project.

method in ArangoRepository extension class using COLLECT in query annotation to group by and count not working

I have a simple node like this below
#Document("users")
public class User {
#Id // db document field: _key
private String id;
#ArangoId // db document field: _id
private String arangoId;
private String firstName;
private String lastName;
private String country;
public User() {
super();
}
public User(String id) {
this.id = id;
}
public User(String id, String country) {
this.id = id;
this.country = country;
}
// getter & setter
#Override
public String toString() {
return "User [id=" + id + ", name=" + firstName + ", surname=" + lastName + "]";
}
public String getId() {
return id;
}
}
here is the repository class but the method getListOfCountryAndNumUsers returns null even though i have inserted users with different countries into the database.
public interface UserRepository extends ArangoRepository<User, String> {
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN
{\"country\" : country, \"count\" : length }")
Iterable<CountryAndNumUsers> getListOfCountryAndNumUsers();
}
I think the problem could be with the the syntax of my query in the query annotation. I didnt see any direct example of using collect operation in the spring data arango db part of arangodb documentation here but I saw the collect operation in the section "high level operations" of arangoDb documentation here
Please Help. Thanks. !
So I discovered my error. It was in a class I didn't add in the question. That is the class for the return object of the method getListOfCountryAndNumUsers()
i.e class CountryAndNumUsers.
public class CountryAndNumUsers {
private String country;
private Integer numberOfUsers;
public CountryAndNumUsers(String country, Integer numberOfUsers) {
this.country = country;
this.numberOfUsers = numberOfUsers;
}
public String getCountry() {
return country;
}
public Integer getNumberOfUsers() {
return numberOfUsers;
}
}
so there was a mapping mismatch since the query returns an object with different field names. I changed the query to this below so that it matches
#Query("FOR u IN users COLLECT country = u.country WITH COUNT INTO length RETURN {\"country\" : country, \"numberOfUsers\" : length }")

Linking two object by code (not ID) using Eclipselink JPA

i have two tables:
area (
id int PK autoincrement
code varchar
)
products (
id int PK autoincrement
area_id int
)
And the objets are defined like this:
class Product {
...
#JoinColumn(name = "area_id", referencedColumnName = "id")
#ManyToOne
#Expose
private Area area;
...
}
This works fine but I want that area to be a String with the code used in the table area column code.
class Product {
...
???
private String area;
...
}
What should be the annotations to make this work?
Thanks!
Try to use a combination of #SecondaryTable and #Column annotations. Something like this:
#Entity
#SecondaryTable(name="area", pkJoinColumns=#PrimaryKeyJoinColumn(name="id", referencedColumnName="area_id"))
class Product {
...
#Column(name="code", table = "area")
private String code;
...
}
If there is some poor soul with the same problem, here is how I did it:
Using transformers. So the field area is defined like this:
#Transformation(fetch = FetchType.EAGER, optional = false)
#ReadTransformer(transformerClass = AreaAttributeTransformer.class)
#WriteTransformers({
#WriteTransformer(
transformerClass = AreaFieldTransformer.class,
column = #Column(name = "area_id", nullable = false))
})
#Expose
private String area;
Then those clases work like this:
AreaAttributeTransformer
public class AreaAttributeTransformer implements AttributeTransformer {
private AbstractTransformationMapping mapping;
#Override
public void initialize(AbstractTransformationMapping abstractTransformationMapping) {
this.mapping = abstractTransformationMapping;
}
#Override
public Object buildAttributeValue(Record record, Object o, Session session) {
for (DatabaseField field : mapping.getFields()) {
if (field.getName().contains("area_id")) {
EntityManager em = MyEntityManagerFactory.getENTITY_MANAGER_FACTORY().createEntityManager();
List results = em.createNamedQuery("Areas.findById")
.setParameter("id", record.get(field))
.getResultList();
if (results.size() > 0)
return ((Area) results.get(0)).getCode();
}
}
return null;
}
}
AreaFieldTransformer
public class AreaFieldTransformer implements FieldTransformer {
private AbstractTransformationMapping mapping;
#Override
public void initialize(AbstractTransformationMapping abstractTransformationMapping) {
this.mapping = abstractTransformationMapping;
}
#Override
public Object buildFieldValue(Object o, String s, Session session) {
if (o instanceof RouSub) {
EntityManager em = MyEntityManagerFactory.getENTITY_MANAGER_FACTORY().createEntityManager();
List results = em.createNamedQuery("Area.findByCode")
.setParameter("area", ((Area) o).getCode())
.getResultList();
if (results.size() > 0)
return ((Area)results.get(0)).getId();
}
return null;
}
}

Creating JPA entity with composite primary key with #Id from #MappedSuperclass

I have a class hierarchy for JPA entities with the base class being a MappedSuperclass which has one ID defined. I am trying to use a composite key in a subclass however that does not seem to work
My code looks like this
#MappedSuperclass
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
#Entity
#EntityListeners(EntityBaseListener.class)
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name = "catalog_entity")
public class BaseCatalogEntity extends BaseEntity {
#Column(name = "created_at", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
#Column(name = "updated_at", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date updatedAt;
public void setCreatedAt(Date date)
{
createdAt = date;
}
public void setUpdatedAt(Date date)
{
updatedAt = date;
}
public Date getCreatedAt() {
return createdAt;
}
public Date getUpdatedAt() {
return updatedAt;
}
}
#Entity
#Table(schema = "student_catalog")
#IdClass(value = StudentCatalog.StudentCatalogPK.class)
public class StudentCatalog extends BaseCatalogEntity {
#Id
#Column(name = "name", nullable = false, length = 100)
private String name;
#Id
#Column(name = "version", nullable = false)
private Integer version;
#Column(name = "description" , length = 255)
private String description;
#Column(name = "vendor" , length = 50)
private String vendor;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
public static class StudentCatalogPK implements Serializable {
private Long id;
private String name;
private Integer version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
#Override
public boolean equals(Object obj) {
boolean result = false;
if(obj != null && (obj instanceof StudentCatalogPK)) {
StudentCatalogPK other = (StudentCatalogPK)obj;
result = (Objects.equals(this.id, other.id) && Objects.equals(this.name, other.name) &&
Objects.equals(this.version, other.version));
}
return result;
}
#Override
public int hashCode() {
return (27780 + (this.id != null ? this.id.hashCode() : 0) +
(this.version != null ? this.version.hashCode() : 0) +
(this.name != null ? this.name.hashCode() : 0));
}
}
}
I get the following exception:
Exception Description: Invalid composite primary key specification. The names of the primary key fields or properties in the primary key class [com.example.jpa.StudentCatalog$StudentCatalogPK] and those of the entity bean class [class com.example.jpa.StudentCatalog] must correspond and their types must be the same. Also, ensure that you have specified ID elements for the corresponding attributes in XML and/or an #Id on the corresponding fields or properties of the entity class.
I am using Eclipselink 2.5.1. Is there a way I can get this to work without changing the BaseEntity and BaseCatalogEntity classes?
It is not legal in JPA to redefine the id in subclasses. This would lead to ambiguities in the table mappings as well as in polymorphic queries.
The desire to extend the key defined in a superclass is a common issue when business keys are used for DB identity. I would advise to use only surrogate keys (like UUID) for DB identity and business keys for instance identity.
Under following conditions:
your base entity should use TABLE_PER_CLASS inheritance (and as I can see it is)
your base entity (composite key) key is of the same type as that one you want to have in your derived class (so there should be also composite key of String and Integer).
You can use #AttributeOverride annotation under class declaration, removing #Id fields from it:
#AttributeOverride(name = "id", column = #Column(name = "NAME"))
This - in result, can change column name in derived entity's table and that's the most you can acheive.
When using #MappedSuperClass, it would be advisable to make the BaseEntity Class as abstract and then extending the Base class from other Entity classes.
Cleaner approach keeping inheritance in mind and designing your application.

Object: entity.ENTITY[ id=null ] is not a known entity type

this is a similar post to one I have seen before regarding this exception but I am utterly lost. I have yet to persist an entity to a database using JPA, although I have read from tables using it no problem. My setup is Netbeans 7.1 using Glassfish 3.1.1, EclipseLink is my persistence provider. I have a very simple scenario where I just want to test writing a persons name and age into the database and having the id auto increment. Its an MySql database with the fields: Id, FirstName and Age. Heres my code:
Web servlet to take in name and age from html form:
#Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userPath = request.getServletPath();
if(userPath.equals("/addUser")){
//get request parameters from form
String name = request.getParameter("name");
String age = request.getParameter("age");
//set request attributes to be used by forwarded page
request.setAttribute("name", name);
request.setAttribute("age", age);
//create manager class to add person to database
Manager manager = new Manager();
manager.addPerson(name, age);
userPath = "/result";
}
// use RequestDispatcher to forward request internally
String url = "/WEB-INF/view" + userPath + ".jsp";
try {
request.getRequestDispatcher(url).forward(request, response);
} catch (Exception ex) {
ex.printStackTrace();
}
}
Manager class that takes in name and age, creates a person object and persists it.
public class Manager {
private static final String PERSISTENCE_UNIT_NAME = "FormPU";
private static EntityManagerFactory factory;
public Manager() {
}
public void addPerson(String name, String age) {
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em = factory.createEntityManager();
Persons persons = new Persons();
persons.setName(name);
persons.setAge(age);
em.getTransaction().begin();
em.persist(persons);
em.getTransaction().commit();
em.close();
}
}
Persons entity class:
/**
*
* #author esmiala
*/
#Entity
#Table(name = "persons")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Persons.findAll", query = "SELECT p FROM Persons p"),
#NamedQuery(name = "Persons.findById", query = "SELECT p FROM Persons p WHERE
p.id = :id"),
#NamedQuery(name = "Persons.findByFirstName", query = "SELECT p FROM Persons p
WHERE p.firstName = :firstName"),
#NamedQuery(name = "Persons.findByAge", query = "SELECT p FROM Persons p WHERE
p.age = :age")})
public class Persons implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "Id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 255)
#Column(name = "FirstName")
private String firstName;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 255)
#Column(name = "Age")
private String age;
public Persons() {
}
public Persons(Integer id) {
this.id = id;
}
public Persons(Integer id, String firstName, String age) {
this.id = id;
this.firstName = firstName;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Persons)) {
return false;
}
Persons other = (Persons) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "entity.Persons[ id=" + id + " ]";
}
}
Persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com
/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="FormPU" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/form</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties/>
</persistence-unit>
</persistence>
Note: I have also tried setting exclude-unlisted-classes tag to true and list the class seperately but that didn't work either.
The exception:
WARNING: StandardWrapperValve[Controller]: PWC1406: Servlet.service() for servlet
Controller threw exception
java.lang.IllegalArgumentException: Object: entity.persons[ id=null ] is not a
known entity type.
atorg.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerNewObject
ForPersist(UnitOfWorkImpl.java:4141)
atorg.eclipse.persistence.internal.jpa.EntityManagerImpl.
persist(EntityManagerImpl.java:368)
at manager.Manager.addPerson(Manager.java:36)
at controller.Controller.doPost(Controller.java:70)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:754)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:847)
at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523)
...and so on. Any help would be greatly appreciated!
<exclude-unlisted-classes> doesn't work as you would expect - the very presence of this element in persistence.xml disables automatic discovery of #Entity classes, no matter what's inside it.
Also, #Entity(name="persons") is probably not what you want, use #Entity #Table (name="persons") instead.
So you say you can read the class fine, but get an error persisting a new instance?
Can you update an object that you read?
It seems you are having some kind of class loader issue. Somehow you have the class on your classpath twice, or have two different class loaders. The object you are passing to persist is from a different class loader than the one JPA is using. You can check the class loader of what was read, and of the object being persisted to see how they differ.
Have you redeployed you app, or hotdeployed? Does it work if you shut down/restart the server properly. Ensure you are closing your old EntityManagerFactory before redeploying.
Concerning youe concrete problem, try to see if this link helps.
Anyway, the way you are instantiating the EntityManager is not thread safe.
You can see here why. Or, better, you can use NetBeans' wizard for creating JPA controller classes from entity classes, and see how it injects the EntityManager:
#PersistenceContext
private EntityManager em;
See also that the controller classes (the equivalent of your Manager POJO) have the Stateless annotation. This is because you can safely inject an EJB (in this case the EntityManager) only in an object whose lifecycle is managed by the web container (see here for further reference about Accessing Enterprise Beans).