Adding a join to a JPA CriteriaQuery - join

I have a CriteriaQuery on my FlowStep object.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<FlowStep> cbQuery = cb.createQuery(FlowStep.class);
Root<FlowStep> flowStep = cbQuery.from(FlowStep.class);
List<Predicate> predicates = new ArrayList<>();
Then I add many predicates depending on what I want to filter.
// add all the conditions to query
cbQuery.select(flowStep).where(predicates.toArray(new Predicate[0]));
I even add sorting stuff.
cbQuery.orderBy(orders);
Query query = em.createQuery(cbQuery);
List<FlowStep> resultList = query.getResultList();
But I need to add a join like this:
select * from flowstep fs join flowinstance fi on fi.id = fs.flowinstanceid and fi.domainid = 'Test'
so I only want to return the flow steps matching the criteria AND which are in the Test domain, and domain information is in table flowinstance.
How do you add join to a CriteriaQuery?
I saw something like
Join<FlowStep, UUID> flowStepFlowInstanceJoin = flowStep.join(FlowInstance_.id);
but then I need to add the condition on the domain being equal to a value.
Is it possible to use a join like the above on a JPQL criteria query?
The initial answer was to add this before the Predicate list:
Join<FlowStep, FlowInstance> flowStepFlowInstanceJoin = flowStep.join("id", JoinType.LEFT);
flowStepFlowInstanceJoin.on(cb.equal(flowStepFlowInstanceJoin.get("domainid"), domain));
FlowStep has a flowinstanceid column and field, and flowinstance has an id field.
This compiles but does not work.
I get an error "Cannot join to attribute of basic type".
So there needs to be a one-to-many relationship between FlowStep and FlowInstance?
A FlowInstance has many flow steps so maybe
#Column(name = flowinstanceid)
private UUID flowInstanceId;
in FlowStep class needs to be changed to a JoinColumn? And add a OneToMany or ManyToOne relationship to make the above JOIN possible?

The solution was suggested by CriteriaBuilder join two tables with a custom condition and JPA many-to-one relation - need to save only Id.
In class FlowStep, we need to add the FlowInstance object and a ManyToOne annotation:
#JoinColumn(name = FLOW_INSTANCE_ID, insertable = false, updatable = false)
#ManyToOne(targetEntity = FlowInstance.class, fetch = FetchType.EAGER)
#JsonIgnore
private FlowInstance flowInstance;
Note the class already had a field for the flow instance id of type UUID, but a field of class FlowInstance is necessary, it seems, for the ManyToOne relationship.
Then when building the JPA query:
Join<FlowStep, FlowInstance> flowStepFlowInstanceJoin = flowStep.join("flowInstance", JoinType.INNER);
flowStepFlowInstanceJoin.on(cb.equal(flowStepFlowInstanceJoin.get("domainId"), domain));
This goes below the
Root<FlowStep> flowStep = cbQuery.from(FlowStep.class);
line.
This makes it work.
It's very important that the join is of type INNER so that it only returns the steps having the wanted domain. LEFT would return steps with a non-wanted domain, and RIGHT would return as many null rows as steps having the wanted domain.

Related

Map cypher query result to domain object

I just started to use Neo4j with Spring Data and I'm not able to recover graph objects and convert them back to domain objects. I must say I have no previous experience in that kind of databases.
In that way, I'm using Spring Data repositories. For standard queries the repository code is auto-generated but I would like to define some custom methods, so I created my custom repository as explained here.
For example, I would like to be able to update a certain property value (currentValue property in this case) from a given edge between two certain nodes (searchByUserName is a previously defined index in my node entity which represents a user). I'm using the query method from the Neo4j template in my custom repository implementation as follows:
public class TwitterUserRepositoryImpl implements TwitterUserRepositoryCustom{
#Autowired
private Neo4jOperations neo4jTemplate;
public void updateRelationshipValueByUserName(
String userAUserName, String userBUserName, double value){
HashedMap params = new HashedMap();
params.put("userAUserName", userAUserName);
params.put("userBUserName", userBUserName);
params.put("value", value);
String query = "START x=node:searchByUserName(userName = {userAUserName}), " +
"y=node:searchByUserName(userName = {userBUserName})" +
" MATCH (x)-[r:FOLLOWS]->(y)" +
" SET r.currentValue = {value}" +
" RETURN r";
Result<Map<String, Object>> relationships = neo4jTemplate.query(query, params);
/* let's try to recover the relationship entity and do some more stuff */
}
The cypher query returns an "edge" between two users, where its relationship type is "FOLLOWS", simulating a Twitter users network. I have no idea how to convert this QueryResult object back to my RelationshipEntity object. Is that possible?
Just use the result-dsl: http://static.springsource.org/spring-data/data-graph/snapshot-site/reference/html/#d5e1118
relationships.to(MyRelationshipEntity.class)
will return you a Result<MyRelationshipEntity> which is an Iterable

Adding to EntitySet not working

I'm trying to add an object to a database-first ORM EntitySet in an MVC project. I use a piece of code something like this:
public static Boolean CreateListing(string title, string description)
{
ListingEntities ce = new ListingEntities();
ce.Ads.AddObject(new Ad()
{
ID = Guid.NewGuid(),
Title = title,
Description = description,
});
return ce.SaveChanges() == 1;
}
However, the SaveChanges method throws a Data.UpdateException which is thrown by a SqlClient.SqlException. The latter says
"Cannot insert the value NULL into column 'ID', table 'Listings.dbo.Ads'; column does not allow nulls. INSERT fails.
The statement has been terminated."
I wholeheartedly agree. I just don't see why the ID should be null when it seems I set it immediately prior. Any suggestions?
Thanks,
Nathan
Someone else on my team configured the database to create its own ID's, and the issue is resolved.

Getting only what is needed with Entity Framework

With a plain connection to SQL Server, you can specify what columns to return in a simple SELECT statement.
With EF:
Dim who = context.Doctors.Find(3) ' Primary key is an integer
The above returns all data that entity has... BUT... I would only like to do what you can with SQL and get only what I need.
Doing this:
Dim who= (From d In contect.Doctors
Where d.Regeneration = 3
Select New Doctor With {.Actor = d.Actor}).Single
Gives me this error:
The entity or complex type XXXXX cannot be constructed in a LINQ to Entities query.
So... How do I return only selected data from only one entity?
Basically, I'm not sure why, but Linq can't create the complex type. It would work if you were creating a anonymous type like (sorry c# code)
var who = (from x in contect.Doctors
where x.Regeneration == 3
select new { Actor = x.Actor }).Single();
you can then go
var doctor = new Doctor() {
Actor = who.Actor
};
but it can't build it as a strongly typed or complex type like you're trying to do with
var who = (from x in contect.Doctors
where x.Regeneration == 3
select new Doctor { Actor = x.Actor }).Single();
also you may want to be careful with the use of single, if there is no doctor with the regeneration number or there are more than one it will throw a exception, singleordefault is safer but it will throw a exception if there is more than one match. First or Firstordefault are much better options First will throw a exception only if none exist and Firstordefault can handle pretty much anything
The best way to do this is by setting the wanted properties in ViewModel "or DTO if you're dealing with upper levels"
Then as your example the ViewModel will be:
public class DoctorViewModel{
public string Actor {get;set;}
// You can add as many properties as you want
}
then the query will be:
var who = (From d In contect.Doctors
Where d.Regeneration = 3
Select New DoctorViewModel {Actor = d.Actor}).Single();
Sorry i wrote the code with C# but i think the idea is clear :)
You can just simply do this:
Dim who= (From d In contect.Doctors
Where d.Regeneration = 3
Select d.Actor).Single
Try this
Dim who = contect.Doctors.SingleOrDefault(Function(d) d.Regeneration = 3).Actor

Executing Method Against DataContext Returning Inserted ID

Is there any way to use DataContext to execute some explicit SQL and return the auto-increment primary key value of the inserted row without using ExecuteMethodCall? All I want to do is insert some data into a table and get back the newly created primary key but without using LINQ (I use explicit SQL in my queries, just using LINQ to model the data).
Cheers
EDIT: Basically, I want to do this:
public int CreateSomething(Something somethingToCreate)
{
string query = "MyFunkyQuery";
this.ExecuteCommand(query);
// return back the ID of the inserted value here!
}
SOLUTION
This one took a while. You have to pass a reference for the OUTPUT parameter in your sproc in your parameter list of the calling function like so:
[Parameter(Name = "InsertedContractID", DbType = "Int")] ref System.Nullable<int> insertedContractID
Then you have to do
insertedContractID = ((System.Nullable<int>)(result.GetParameterValue(16)));
once you've called it. Then you can use this outside of it:
public int? CreateContract(Contract contractToCreate)
{
System.Nullable<int> insertedContractID = null; ref insertedContractID);
return insertedContractID;
}
Take heavy note of GetParameterValue(16). It's indexed to whichever parameter it is in your parameter list (this isn't the full code, by the way).
You can use something like this:
int newID = myDataContext.ExecuteQuery<int>(
"INSERT INTO MyTable (Col1, Col2) VALUES ({0}, {1});
SELECT Convert(Int, ##IDENTITY)",
val1, val2).First();
The key is in converting ##IDENTITY in type int, like Ben sugested.
If you insist on using raw sql queries, then why not just use sprocs for your inserts? You could get the identity returned through an output parameters.
I'm not the greatest at SQL, but I broke out LinqPad and came up with this. It's a big hack in my opinion, but it works ... kinda.
DataContext.ExecuteQuery<T>() returns an IEnumerable<T> where T is a mapped linq entity. The extra select I added will only populate the YourPrimaryKey property.
public int CreateSomething(Something somethingToCreate)
{
// sub out your versions of YourLinqEntity & YourPrimaryKey
string query = "MyFunkyQuery" + "select Convert(Int, SCOPE_IDENTITY()) as [YourPrimaryKey]";
var result = this.ExecuteQuery<YourLinqEntity>(query);
return result.First().YourPrimaryKey;
}
You'll need to modify your insert statement to include a SELECT ##Identity (SQL Server) or similar at the end.

alternated default sort in GRAILS

why the following Groovy snippet returns alternating
[Account: 2222 and 2222, Account: 1111 and 1111] or
[Account: 1111 and 1111, Account: 2222 and 2222]
if you run it multiple times within the Groovy Console ?
I thought the sort statement leads to an ALWAYS descending sort order of the list ???
class Account {
long number
String code
String toString() {return "Account: $number and $code"}
static mapping = {
sort number:"desc"
}
}
List items = []
items << new Account(number:1111,code:'1111')
items << new Account(number:2222,code:'2222')
println items.sort()
Thanks in advance
Dominik
You don't define an ordering among your Account instances. The mapping directive is only applicable to GORM mapped classes (IOW: "domain objects"), and will only be used when loading instances of your class from the database AFAIK.
However, you are appending the objects to a plain List, which does not know about GORM properties. In order to sort lists of Account instances reliably in such a context, you will have to specify an explicit ordering, for example:
class Account implements Comparable {
...
def int compareTo(rhs) {
long onum = rhs.number;
return (onum > number)? -1 : ((onum < number)? 1 : 0);
}
...
}
This article has more information about the topic. As to why Groovy sorts the list differently on multiple calls to list.sort: well, I have no idea...
Grails has two main default ways to sort:
Sort when you query:
def airports = Airport.list(sort:'name')
Put a default sorting method on that object:
class Airport {
…
static mapping = {
sort name:"desc"
}
}
The above is taken from the grails documentation.

Resources