I have
#Document
public class Employee
{
#Id
Long empCode;
String empSurname;
String address;
// getters setters
}
I have written a query using Mongo Repositories like
public interface EmployeeRepository extends MongoRepository<Employee, Long>
{
List<Employee> findEmployeesByEmpCode(int empCode);
#Query(value="{ 'empCode' : ?0 }", fields="{ 'address' : 1}")
List<String> findAddressByEmpCode(int empCode);
The first query works but the second fails. It works only if I change the returned type of List to Employee.
The error at failure is
java.lang.IllegalArgumentException: [Assertion failed] - this argument is required; it must not be null
at org.springframework.util.Assert.notNull(Assert.java:115)
at org.springframework.util.Assert.notNull(Assert.java:126)
at org.springframework.data.convert.EntityInstantiators.getInstantiatorFor(EntityInstantiators.java:86)
at org.springframework.data.mongodb.repository.query.DtoInstantiatingConverter.<init>(DtoInstantiatingConverter.java:61)
at org.springframework.data.mongodb.repository.query.MongoQueryExecution$ResultProcessingConverter.convert(MongoQueryExecution.java:376)
at org.springframework.data.mongodb.repository.query.MongoQueryExecution$ResultProcessingExecution.execute(MongoQueryExecution.java:345)
at org.springframework.data.mongodb.repository.query.AbstractMongoQuery.execute(AbstractMongoQuery.java:91)
Obviously, it cannot work out how to convert the values from the contained Employee object to String.
How can I implement this ? Any example would be much appreciated.
Thanks,
The fields attribute is used to filter properties which are not set into your domain objects (Employee). It has not much to do with MongoDB projection mechanism.
In order to use projection here, see projections in the reference documentation.
You will have to create a projection interface:
interface AddressesProjection { String getAddress(); }
and change the repository method signature accordingly:
List<AddressesProjection> findByEmpCode(int empCode);
Projections declare a contract between the underlying type and the method signatures related to the exposed properties. Hence it is required to name getter methods according to the property name of the underlying type.
This is the closest you can get, afaik.
Related
I am using Spring Boot application. I am using following method to return List<String> using #Query
annotation.
#Query("select c.name from Customer c")
List<String> findAllNames();
Is there a way to get the above result without #Query or #NativeQuery annotations?
Spring Data JPA supports this quite well. You can define a POJO that represents the (reduced) object (the projection) you want of the entity, then define a method that describes the query, returns the appropriate type, and Spring Data will generate all the code necessary to execute the query and map the results.
Here's an example of a custom repository interface that uses this technique (untested pseudo-code):
public class CustomerSummary {
private String name;
// constructor and getters
}
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<CustomerSummary> getAllSummaries();
}
I'm not sure if you can use "simple" types such as String in the return type, but I wouldn't be surprised if that is supported.
Also, you might have to tweak the exact method naming of `` to make sure Spring Data correctly interprets it as you want. Here is some additional reference documentation.
You could use projection to select only name property:
interface CustomerNameProjection {
String getName();
}
and then use it in Repository class:
List<CustomerNameProjection> findAllProjectedBy();
edit: corrected repository method name
Inject EntityManager and call createQuery.
entityManager.createQuery("select c.name from Customer c")
.getResultList()
UPDATE:
I have tried implementing the SQLData interface for my Financial.class and PersonalInfo.class Java POJOs defined on my #NamedStoredProcedureQuery for the type. I also implemented the required getSQLTypeName, readSQL, and writeSQL methods per an Oracle doc:
https://docs.oracle.com/cd/A97335_02/apps.102/a83724/samapp6.htm
I was hoping this would work, but it looks like its still giving me the same Type cannot be null exception. Does it matter that these personal_information_t and financial_t Objects defined in my Oracle DB are inheriting from a superclass, called base_t ?
Hi guys, I'm simply trying to use the #NamedStoredProcedureQuery directly on my #Entity class to call a Stored Procedure that is in my Oracle Database (that is in a separate schema, "JJR"). I can indeed correctly connect to this database programmatically from my Java Spring Boot application, and I can run JPA queries successfully like .findAll() so I know its not a connection issue but I believe something to do with my #NamedStoredProcedure declaration. All the tutorials on Google for Spring JPA Stored Procedure queries are using standard data types like Long or String, and only returning one OUT parameter.
However, I'm returning two OUT parameters and to make it more complicated, they are user-defined types (defined in the Oracle Database), so I'm trying to figure out how to handle this as my current attempt is returning back this exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Type cannot be null; nested exception is java.lang.IllegalArgumentException:
My Stored Procedure structure (It is inside a Package in my oracle db of pkg_employee_data in schema/user JJR). The user-defined types (personal_information_t and financial_t both have multiple fields in them, i.e. personal_information_t has an userid, firstname, lastname, and financial_t has salary, networth etc..:
PROCEDURE get_employee_data (
empid IN emp.emp_id%TYPE, // NUMBER(38,0)
persinfo OUT personal_information_t, // user-defined type
financ OUT financial_t / user-defined type
);
And how I'm defining the #NamedStoredProcedureQuery
#Entity
#NamedStoredProcedureQuery(name = "Employee.getEmployeeData",
procedureName = "pkg_employee_data.get_employee_data", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "empid", type = Long.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "persinfo", type = PersonalInfo.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "financ", type = Financial.class)})
public class Employee {
// Should I list the columns I want mapped here in the Entity
// from the "persinfo" and "financ" user-defined types, that
// should have a bunch fields/data inside them?
#Id
private long userId;
private String firstName;
private Double salary;
}
And the #Repository declaration, where I map this method:
#Repository
public interface EmployeeRepository extends JpaRepository<Employee,Long> {
#Procedure(name = "Employee.getEmployeeData")
Map<String, Object> getEmployee(#Param("empid") long empid);
}
Are my type = "*.class" declarations even correct on the #NamedStoredProcedureQuery annotation? seems like my Types ares getting read a null. I created a PersonalInfo.java and Financial.java #Entities in my code as I hoped it would map from the Oracle user-defined types but it doesn't seem like that's working.
On all my #Entities, I have to declare some meta-information like #Table(schema = "JJR", name = "MY_TABLE_NAME") because as I stated above, I'm logging into a specific user/schema in my Oracle DB. Do I need to put this on the #NamedStoredProcedureQuery too? I'm not mapping to a table here technically though (I just need the data from the two OUT parameters).
I even tried using java.sql.Struct.class as the Type in the #NamedStoredProcedureQuery per Google, but nothing seems like it's working.
What actually columns/fields do I need defined in the Employee Entity, which has the #NamedStoredProcedureQuery annotation on it? I'm not really mapping to a Table, like normally we use #Entity. This stored procedure is just returning those two OUT parameters (personal_information_t and financial_t and I need the data from it). Should the fields in Employee class be simply the fields that are in the Oracle user-defined types that I need?
I don't know why Spring doesn't like my code:
I have Entry.java:
#Entity
#Table(name = "entries")
public class Entry {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "text")
private String text;
}
EntryDao.java:
public interface EntryDao extends JpaRepository<Entry, Long> {
List<Entry> findAllEntries();
}
EntryService.java:
#Service
public interface EntryService {
List<Entry> findAllEntries();
}
EntryServiceImpl.java:
public class EntryServiceImpl implements EntryService {
private EntryDao entryDao;
private SessionFactory sessionFactory;
#Override
#SuppressWarnings("unchecked")
public List<Entry> findAllEntries() {
Session session = this.sessionFactory.getCurrentSession();
List<Entry> entries = session.createQuery("from entries").list();
return entries;
}
}
This code gives me an error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entryDao': Invocation of init method failed; nested exception is org.springframework.data.mapping.PropertyReferenceException: No property findAllEntries found for type Entry!
I don't understand how to handle this error and why this error occurs.
The root cause you got the exception is that you're against the convention/rules to declare/create queries in Spring Data JPA.
The official docs of Spring Data JPA mentioned that:
The goal of Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
The central interface of abstraction is Repository, to manage your entity, you need to declare your own interface of Repository and JPA will help you to create proxy instances for those interfaces. There're already some base Repositories like CrudRepository or PagingAndSortingRepository to provide basic functionalities as you can tell from their names, so by extending those basic ones, you'll have many basic methods. To define more specific access methods, you need to follow the ways JPA provided to create queries:
Define method in your interface following the method name convention
Use #Query annotation to define it manually
For the first method, the docs of Query Create has detailed clarification, here's some key idea:
The mechanism strips the prefixes find…By, read…By, query…By, count…By, and get…By from the method and starts parsing the rest of it. The introducing clause can contain further expressions such as a Distinct to set a distinct flag on the query to be created. However, the first By acts as delimiter to indicate the start of the actual criteria. At a very basic level you can define conditions on entity properties and concatenate them with And and Or
Simply speaking, JPA will parse the method name and try to find the related property to create query criteria for you.
Now let's have a look at your code, if you just want to retrieve all of your entities, you don't need to define your own method, there's already findAll methods pre-defined, if you want to retrieve entities based on text content, it's supposed to look like:
Entity findByText(String text)
but your method findAllEntites just don't match any rules, so JPA throws such an error message to you.
As #AbdullahWasi said, just use the existing findAll() method from SpringData for your code. You might want to place a #Transactional annotation in your code, but that depends on your transaction boundaries.
Just remove your custom method from your Dao.
public interface EntryDao extends JpaRepository<Entry, Long> {
}
And use the default spring data findAll
#Transactional
public class EntryServiceImpl implements EntryService {
private EntryDao entryDao;
private SessionFactory sessionFactory;
#Override
#SuppressWarnings("unchecked")
public List<Entry> findAllEntries() {
return entryDao.findAll();
}
}
I want to write a finder method in my repository to find an object based on one field OR another one while supplying one parameter like:
#RepositoryDefinition(domainClass = Person.class, idClass = Long.class)
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findAllByIdOrAnotherId(someId);
}
How can I do that without using SQL?
I added a second parameter to the method and it worked.
List<Transaction> findAllByIdOrParentId(Long id, Long parentId);
This is just a definition for the method because I pass the same parameter to the method from the service as:
List<Transaction> transactions = transactionRepository.findAllByIdOrParentId(transactionId, transactionId);
One of the cool things about Spring Data JPA is the fact you can define custom queries as abstract methods in your interface.
In a nutshell, you can define a new search criteria just by specifying the #Entity field name you want to query by. So, let's say i want to findAll() by Id and CustomId. Both Id and CustomId are fields on my domain class. I would do:
List<Person> findAllByIdOrCustomId(someId, someCustomId);
For more info, research the link below:
Spring Data JPA - DEFINE QUERY METHODS
In Spring CrudRepository, do we have support for "IN clause" for a field? ie something similar to the following?
findByInventoryIds(List<Long> inventoryIdList)
If such support is not available, what elegant options can be considered? Firing queries for each id may not be optimal.
findByInventoryIdIn(List<Long> inventoryIdList) should do the trick.
The HTTP request parameter format would be like so:
Yes ?id=1,2,3
No ?id=1&id=2&id=3
The complete list of JPA repository keywords can be found in the current documentation listing. It shows that IsIn is equivalent – if you prefer the verb for readability – and that JPA also supports NotIn and IsNotIn.
If inventoryId has the primary key, you can simply use yourRepo.findAll(inventoryIdList).
For any method in a Spring CrudRepository you should be able to specify the #Query yourself. Something like this should work:
#Query( "select o from MyObject o where inventoryId in :ids" )
List<MyObject> findByInventoryIds(#Param("ids") List<Long> inventoryIdList);
Yes, that is supported.
Check the documentation provided here for the supported keywords inside method names.
You can just define the method in the repository interface without using the #Query annotation and writing your custom query. In your case it would be as followed:
List<Inventory> findByIdIn(List<Long> ids);
I assume that you have the Inventory entity and the InventoryRepository interface. The code in your case should look like this:
The Entity
#Entity
public class Inventory implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
// other fields
// getters/setters
}
The Repository
#Repository
#Transactional
public interface InventoryRepository extends PagingAndSortingRepository<Inventory, Long> {
List<Inventory> findByIdIn(List<Long> ids);
}