Spring data rest - expose default methods - spring-data-rest

I have a Person Repository as follows
#RepositoryRestResource
public interface PersonRepository extends Repository<Person, String> {
List<Person> findAll();
default List<Person> findNewPersons() {
return findByStartDateAfter(LocalDate.now().minusMonths(3));
}
List<Person> findByStartDateAfter(LocalDate date);
}
I am not able to expose the default method through rest.. is there a way to do it without creating an implementation of the repo ?

I faced a similar problem, and was able to solve it using a SpEL expression inside an HQL query in a #Query annotation.
While nowhere near as clean as using a default method, this was the tidiest way I could find without writing a custom controller or introducing a custom implementation with a new DSL library or something for just this one query.
#Query("select p from Person p where p.startDate > :#{#T(java.time.LocalDate).now().minusMonths(3)}")
List<Person> findNewPersons();
My actual query was different so I might have typoed the syntax here, but the idea is the same and it worked for my case (I was using a LocalDate parameter and finding timestamps on that day by using a findByTimestampBetween style query).

Related

Jpa createSQLQuery returns List<Object> instead of List<Employee>

Trying to make an sql query to get as a result a list of Class "EmployeeCardOrderLink". But this code always returns me an list of Object. Casts doesn't working. I got the right data in this list, but it's just object. In debug i can call methods(Idea suggest according interface of my class), but then i got "class Object doesn't have a such method". And i can't use TypedQuery cause i have old JPA version, it doesn't support this.
#Repository
public class EmployeeCardOrderLinkDAOImpl extends AbstractBasicDAO<EmployeeCardOrderLink> implements EmployeeCardOrderLinkDAO {
//....
#Override
public List<EmployeeCardOrderLink> getLinksByOrderNumber(Integer num) {
List<EmployeeCardOrderLink> result = (ArrayList<EmployeeCardOrderLink>) getSessionFactory().getCurrentSession().createSQLQuery("select * from employee_card_order_links " +
"where trip_order_id = " + num).list();
return result;
}}
You use Hibernate (not JPA), if you are using Session. Hibernate is JPA provider of course. You have to use EntityManager and other related things to use JPA.
You don't need SQL here. SQL always returns list of objects (if you don't use transformers to DTO objects).
Just use HQL (JPQL in JPA)
To get all EmployeeCardOrderLink
getSessionFactory().getCurrentSession()
.createQuery("select link from EmployeeCardOrderLink link").list();
Query "from EmployeeCardOrderLink" will work for Hibernate too (for JPA will not work).

How to use group_concat in hibernate criteria?

I wrote a query in mysql using group_concat like
SELECT c1,group_concat(c2) FROM table1 where sno in(1,4,8,10) group by c1;
and gives my expected result.
Now the same query I want to write using hibernate criteria.
You have two options (depending on your hibernate version).
Override the dialect class
any hibernate version
You will need to subclass your dialect to add group_concat()
Introduce the dialect override class
Create the following class somewhere in your app (e.g. util package)
package com.myapp.util;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;
public class MySQLCustomDialect extends MySQL5Dialect {
public MySQLCustomDialect() {
super();
registerFunction("group_concat",
new StandardSQLFunction("group_concat",
StandardBasicTypes.STRING));
}
}
Map the dialect override class to boot properties
Add the following property to your application.properities
spring.jpa.properties.hibernate.dialect = com.myapp.util.MySQLCustomDialect
Use JPA Metadata Builder Contributor
hibernate 5.2.18 or newer only
Introduce metadata builder class
Create the following class, remember to add package & resolve imports.
public class SqlFunctions implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction( "group_concat",
new StandardSQLFunction( "group_concat",
StandardBasicTypes.STRING ) ); }
}
Map new class in application boot properties
Leave the dialect properties as is
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.metadata_builder_contributor = com.myapp.util.SqlFunctions
Simple answer is No
Why?
Hibernate support only common function/syntax used in multiple database. There ain't any group_concat function in Microsoft SQL Server and may be in other database as well.
Solution:
You have to execute it as Simple SQL Query.
Finally i go through like below code and got expected result
String query="select c1,group_concat(c2) from table1 where sno in (:pageIds) group by c1";
SQLQuery sqlQuery= session.createSQLQuery(query);
sqlQuery.setParameterList("pageIds", myList);
List list= sqlQuery.list();
c1 group_concat(c2)
aaa valu1,value2
bbb value3
ccc value4,value5,value6
Please refer following code snippets
Criteria cr = session.createCriteria(table1.class);
cr.add(Restrictions.in("sno",snoarray));
criteria.setProjection("c1");
criteria.setProjection(Projections.groupProperty("c1"));

findBy URI not working in Spring Data Rest

By default, in Spring Data Rest the #Id of the entity is not exposed. In line with the REST rules, we're supposed to use the URI of the resource to refer to it. Given this assumption, the findBy queries should work if you pass a URI to them, but they don't.
For example, say I have a one-to-many relationship between Teacher and Student. I want to find students by teacher.
List<Student> findByTeacher(Teacher teacher)
http://localhost:8080/repositories/students/search/findByTeacher?teacher=http://localhost:8080/repositories/teachers/1
This doesn't work because the framework is attempting to convert the teacher URI to a Long.
I get this error that says "Failed to convert from type java.lang.String to type java.lang.Long".
Am I missing something?
You could expose #Id s by configuring web intializer
//Web intializer
#Configuration
public static class RespositoryConfig extends
RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(
RepositoryRestConfiguration config) {
config.exposeIdsFor(Teacher.class);
}
}
Its good to change List to Page
List findByTeacher(Teacher teacher)
to
Page<Student> findByTeacher(#Param("teacher) Teacher teacher, Pageable pageable);
Also note #Param annotation is required along with Pageable. The latter is required because return type "Page"
3.Latest snapshots, not milestones work fine
See https://jira.spring.io/browse/DATAREST-502
Depending of your version of Spring Data, it would work as you want or not. If you are with Spring Data 2.4, you need to pass the URI. If you are with a previous version, you need to pass the id.

How do I express this LINQ query using the NHibernate ICriteria API?

My current project is using NHibernate 3.0b1 and the NHibernate.Linq.Query<T>() API. I'm pretty fluent in LINQ, but I have absolutely no experience with HQL or the ICriteria API. One of my queries isn't supported by the IQueryable API, so I presume I need to use one of the previous APIs -- but I have no idea where to start.
I've tried searching the web for a good "getting started" guide to ICriteria, but the only examples I've found are either far too simplistic to apply here or far too advanced for me to understand. If anyone has some good learning materials to pass along, it would be greatly appreciated.
In any case, the object model I'm querying against looks like this (greatly simplified, non-relevant properties omitted):
class Ticket {
IEnumerable<TicketAction> Actions { get; set; }
}
abstract class TicketAction {
Person TakenBy { get; set; }
DateTime Timestamp { get; set; }
}
class CreateAction : TicketAction {}
class Person {
string Name { get; set; }
}
A Ticket has a collection of TicketAction describing its history. TicketAction subtypes include CreateAction, ReassignAction, CloseAction, etc. All tickets have a CreateAction added to this collection when created.
This LINQ query is searching for tickets created by someone with the given name.
var createdByName = "john".ToUpper();
var tickets = _session.Query<Ticket>()
.Where(t => t.Actions
.OfType<CreateAction>()
.Any(a => a.TakenBy.Name.ToUpper().Contains(createdByName));
The OfType<T>() method causes a NotSupportedException to be thrown. Can I do this using ICriteria instead?
try something like this. It's uncompiled, but it should work as long as IEnumerable<TicketAction> Actions and Person TakenBy is never null. If you set it to an empty list in the ticket constructor, that will solve a problem with nulls.
If you add a reference to the Ticket object in the TicketAction, you could do something like this:
ICriteria criteria = _session.CreateCriteria(typeof(CreateAction))
.Add(Expression.Eq("TakenBy.Name", createdByName));
var actions = criteria.List<CreateAction>();
var results = from a in criteria.List<>()
select a.Ticket;
In my experience, nhibernate has trouble with criteria when it comes to lists when the list is on the object side - such as is your case. When it is a list of values on the input side, you can use Expression.Eq. I've always had to find ways around this limitation through linq, where I get an initial result set filtered down as best as I can, then filter again with linq to get what I need.
OfType is supported. I'm not sure ToUpper is though, but as SQL ignores case it does not matter (as long as you are not also running the query in memory...). Here is a working unit test from the nHibernate.LINQ project:
var animals = (from animal in session.Linq<Animal>()
where animal.Children.OfType<Mammal>().Any(m => m.Pregnant)
select animal).ToArray();
Assert.AreEqual("789", animals.Single().SerialNumber);
Perhaps your query should look more like the following:
var animals = (from ticket in session.Linq<Ticket>()
where ticket.Actions.OfType<CreateAction>().Any(m => m.TakenBy.Name.Contains("john"))
select ticket).ToArray();

NHibernate testing, mocking ISession

I am using NHibernate and Rhinomocks and having trouble testing what I want. I would like to test the following repository method without hitting the database (where _session is injected into the repository as ISession):
public class Repository : IRepository
{
(... code snipped for brevity ...)
public T FindBy<T>(Expression<Func<T, bool>> where)
{
return _session.Linq<T>().Where(where).FirstOrDefault();
}
}
My initial approach is to mock ISession, and return an IQueryable stub (hand coded) when Linq is called. I have a IList of Customer objects I would like to query in memeory to test my Linq query code without hitting the db. And I'm not sure what this would look like. Do I write my own implementation of IQueryable? If so, has someone done this for this approach? Or do I need to look at other avenues?
Thanks!
How I've done this test is to not pass the expression to the repository, instead expose IQueryable giving the repository an interface such as:
public interface IRepository<T>
{
IQueryable<T> All();
// whatever else you want
}
Easily implemented like so:
public IQueryable<T> All()
{
return session.Linq<T>();
}
This means that instead of calling your method on the repository like:
var result = repository.FindBy(x => x.Id == 1);
You can do:
var result = repository.All().Where(x => x.Id == 1);
Or the LINQ syntax:
var result = from instance in repository.All()
where instance.Id == 1
select instance;
This then means you can get the same test by mocking the repository out directly which should be easier. You just get the mock to return a list you have created and called AsQueryable() on.
As you have pointed out, the point of this is to let you test the logic of your queries without involving the database which would slow them down dramatically.
From my point of view is this would be considered Integration Testing. NHibernate has it's own tests that it passes and it seems to me like you're trying duplicate some of those tests in your own test suite. I'd either add the NHibernate code and tests to your project and add this there along with their tests, thats if they don't have one very similiar, and use their methods of testing or move this to an Integration testing scenario and hit the database.
If it's just the fact you don't want to have to setup a database to test against you're in luck since you're using NHibernate. With some googling you can find quite a few examples of how to use SQLite to "kinda" do integration testing with the database but keep it in memory.