I have following entities:
class Person {
int id;
String name;
List<Address> addresses
}
class Address {
int id;
String city;
}
I try to prepare query (based on Criteria-API) where result (each tuple in result List) will contain three elements:
persron.id
person.name
person.addresses <-- collection with 0 or more elements
(1, "Peter", Collection{2,3}) or
(1, "Peter", Tuple{2,3})
I have tried something like this:
EntityManager em;
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = qb.createTupleQuery()
Root<Person> root = cq.from(Person.class);
ListJoin<PersonalData, Address> join = (ListJoin)root.join("addresses", JoinType.LEFT);
cq.multiselect(root.get("id"), root.get("name"), join.get("id"));
TypedQuery<Tuple> tq = em.createQuery(cq);
List<Tuple> result = tq.getResultList();
But received result is different that expected :(
For model: Person(1, "Peter")
which has two addresses
Address(2, "London");
Address(3, "Paris");
my result list is something like Cartesian product:
(1, "Peter", 2), (1, "Peter", 3)
Is is possible to receive result which was requested at the beginning of this post?
No, it can not work the way you are expecting, as the join between person and address will result in a single address per row, and JPA will return the data in a similar format. One person.id, person.name, person.address in each tuple. Why not just return the Person instance and use that?
Related
I have a kStream of Universities -
when University is -
University(universityId: String, name: String, studentIds: Seq[String])
val universityKStream = builder.stream[String, University](...)
And a kTable of Students,
when Student is -
Student(studentId: String, name: String)
val studentsKtable = builder.table[String, Student](...)
I want to join the two and produce to a topic of ResolvedUniverity objects:
ResolvedUniversity(universityId: String, name: String, students: Seq[Student])
I cant groupBy and aggregate students with universityId, since universityId field doesn't exist in Student object..
Using just the DSL, I think the simplest you can do is (Java):
class Student {
String studentId;
String name;
}
class University {
String universityId;
String name;
List<String> studentIds;
}
class ResolvedUniversity {
String universityId;
String name;
List<Student> students;
}
Serde<String> stringSerde = null;
Serde<Student> studentSerde = null;
Serde<University> universitySerde = null;
Serde<ResolvedUniversity> resolvedUniversitySerde = null;
KStream<String, University> universities = topology
.stream("universities", Consumed.with(stringSerde, universitySerde));
KTable<String, Student> students = topology
.table("students", Consumed.with(stringSerde, studentSerde));
KTable<String, ResolvedUniversity> resolvedUniversities = universities
.flatMap((k, v) -> {
return v.studentIds.stream()
.map(id -> new KeyValue<>(id, v))
.collect(Collectors.toList());
})
.join(students, Pair::pair, Joined.with(stringSerde, universitySerde, studentSerde))
.groupBy((k, v) -> v.left().universityId)
.aggregate(ResolvedUniversity::new,
(k, v, a) -> {
a.universityId = v.left().universityId;
a.name = v.left().name;
a.students.add(v.right());
return a;
},
Materialized.with(stringSerde, resolvedUniversitySerde));
With this type of join, for historical processing your KTable of universities must be "primed" with its data before the KStream is joined against it.
I have the following Hibernate classes in Scala, where one Group has many Items. Note that the #Id of the Group class has an autoincrement annotation.
#Entity
#Table(name = "items")
class Item extends Serializable {
#Id
#ManyToOne
#JoinColumn(name="group_sk", nullable=false)
var group: Group = _
#Id
var index: Int = _
var name: String = _
def canEqual(a: Any) = a.isInstanceOf[Item]
override def equals(that: Any): Boolean =
that match {
case that: Item => that.canEqual(this) && this.hashCode == that.hashCode
case _ => false
}
override def hashCode: Int = {
val prime = 31
var result = 1
result = prime * result + group.sk;
result = prime * result + index
return result
}
}
#Entity
#Table(name = "groups")
class Group {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "group_generator")
#SequenceGenerator(name="group_generator",
sequenceName = "GroupSeq", allocationSize = 1)
var sk: Int = _
#Column(name = "group_name")
var name: String = _
#OneToMany
#JoinColumn(name="group_sk")
var items: java.util.List[Item] = _
}
I try to insert one group with a related item, where both the Group should have an auto incremented Id:
session.beginTransaction
val group = new Group
group.name = "Group name"
group.items = new java.util.ArrayList[Item]()
val item1 = new Item
item1.group = group
item1.index = 1
item1.name = "Item 1"
group.items.add(item1)
session.save(group)
session.getTransaction.commit
The exception I get is
Caused by: javax.persistence.OptimisticLockException: Batch update
returned unexpected row count from update [0]; actual row count: 0;
expected: 1
And the Hibernate sql log shows:
Hibernate: select GroupSeq.nextval from dummy
Hibernate: insert into groups (group_name, sk) values (?, ?)
Hibernate: update items set group_sk=? where index=? and group_sk=?
Note that the last update statement doesn't make sense because you won't update the value of group_sk where the column is also in the condition. Moreover, there's no insert statement of the items table.
How to fix this problem?
You need to set cascade type on your OneToMany relationship to tell Hibernate how to mange Items collection of the Group class. So for example something like this should work:
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="group_sk")
var items: java.util.List[Item] = _
I want to map the results of an aggregation to an POJO without iterating through the raw results. The POJO is a field in the Collection on which I'm running the aggregation.
MatchOperation match = match(Criteria.where("drill").is(drill));
SortOperation sort = sort(DESC, "creationDate");
GroupOperation group = group("athlete").first("athlete").as("athlete");
LimitOperation limit = limit(10);
ProjectionOperation project = project("athlete");
Aggregation aggregation = newAggregation(match, sort, group, limit, project);
AggregationResults<Athlete> results = mongoTemplate.aggregate(aggregation, DrillResultInfo.class, Athlete.class);
List<Athlete> mappedResult = results.getMappedResults();
It returns the correct number of objects, but they have as the id the map of the object and the other properties are null.
The result:
id: { "_id" : { "$oid" : "57cd46780348276373579821"} , "_class" : "Athlete" , "firstName" : "Jenny" , "lastName" : "Smith" ....}
The rest of the properties are null.
The Collection:
public class DrillResultInfo {
#Id
private String id;
private Long resultId;
#DBRef
private Athlete athlete;
#DBRef
private Drill drill;
....
}
(.... represents left out data)
Update:
I've made some updates to the code to get it to work:
List<Athlete> respone = new ArrayList<>();
MatchOperation match = match(Criteria.where("drill").is(drill));
SortOperation sort = sort(DESC, "creationDate");
GroupOperation group = group("athlete");
LimitOperation limit = limit(5);
SkipOperation skip = skip(skipElements);
ProjectionOperation project = project("_id");
TypedAggregation<DrillResultInfo> agg = newAggregation(DrillResultInfo.class, match, sort, group, skip, limit, project);
AggregationResults<Object> results = mongoTemplate.aggregate(agg, Object.class);
List<Object> mappedResult = results.getMappedResults();
for (Object obj : mappedResult) {
Athlete ath = (Athlete) ((LinkedHashMap) obj).get("_id");
respone.add(ath);
}
return respone;
I would like to get rid of that for.
Our programming involves some Mock testing using In-Memory Data. Therefore, we implemented the following code that would first create In-Memory Data of Customer objects
// Let us create some in-memory data
// Create a list of Customer
List<Customer> listOfCustomers = new List<BlahBlahExample.Domain.Objects.Customer>()
{ new Customer { CustomerID = "1 ",Orders = new HashSet<Order>(), CustomerDemographics = new HashSet<CustomerDemographic>(), CompanyName = "Chicago Bulls", ContactName = "Michael Jordan", ContactTitle = "top basket ball player", Address = "332 testing lane", City = "Chicago", Region = "Illinois", PostalCode = "484894", Country = "USA", Phone = "3293993", Fax = "39393" },
new Customer { CustomerID = "2 ",Orders = new HashSet<Order>(),CustomerDemographics = new HashSet<CustomerDemographic>() , CompanyName = "Miami Heat", ContactName = "Lebron James", ContactTitle = "second best basket ball player", Address = "90 test street", City = "Miami", Region = "Florida", PostalCode = "4869394", Country = "USA", Phone = "3293213", Fax = "33393" },
new Customer { CustomerID = "3 ",Orders = new HashSet<Order>(),CustomerDemographics = new HashSet<CustomerDemographic>() , CompanyName = "Oklahoma City Thunder", ContactName = "Kevin Durant", ContactTitle = "current top basket ball player", Address = "35 test row", City = "Oklahoma City", Region = "Oklahoma", PostalCode = "480290", Country = "USA", Phone = "304923", Fax = "33325" }
};
// Convert the list to an IQueryable list
IQueryable<Customer> queryableListOfCustomerInMemoryData = listOfCustomers.AsQueryable();
// Let us create a Mocked DbSet object.
Mock<DbSet<BlahBlahExample.Domain.Objects.Customer>> mockDbSet = new Mock<DbSet<BlahBlahExample.Domain.Objects.Customer>>();
// Force DbSet to return the IQueryable members
// of our converted list object as its
// data source
mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.Provider).Returns(queryableListOfCustomerInMemoryData.Provider);
mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.Expression).Returns(queryableListOfCustomerInMemoryData.Expression);
mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.ElementType).Returns(queryableListOfCustomerInMemoryData.ElementType);
mockDbSet.As<IQueryable<BlahBlahExample.Domain.Objects.Customer>>().Setup(m => m.GetEnumerator()).Returns(queryableListOfCustomerInMemoryData.GetEnumerator());
mockDbSet.Setup(m => m.Add(It.IsAny<Customer>())).Callback<Customer>(listOfCustomers.Add);
Mock<BlahBlahAuditMappingProvider> jsAudtMppngPrvdr = new Mock<BlahBlahAuditMappingProvider>();
Mock<BlahBlahDataContext> fctry = new Mock<BlahBlahDataContext>(jsAudtMppngPrvdr.Object);
Mock<BlahBlahDataContext> qryCtxt = new Mock<BlahBlahDataContext>();
Mock<BlahBlahAuditContext> audtCtxt = new Mock<BlahBlahAuditContext>();
Mock<BlahBlahDataContext> mockedReptryCtxt = new Mock<BlahBlahDataContext>();
mockedReptryCtxt.Setup(q => q.Customers).Returns(mockDbSet.Object);
mockedReptryCtxt.Setup(q => q.Set<Customer>()).Returns(mockDbSet.Object);
mockedReptryCtxt.CallBase = true;
DbSet<Customer> inMemoryDbSetCustomer = mockedReptryCtxt.Object.Set<Customer>();
In the next excerpt of code( which is our "Code Under Test"), I add a new Customer to the existing In-Memory Data, and then Invoke SaveChanges on the Mocked Object.
Customer returnCust = (Customer)(mockedReptryCtxt.Object.Set<Customer>().Add(new Customer { CustomerID = "4 ", Orders = new HashSet<Order>(), CustomerDemographics = new HashSet<CustomerDemographic>(), CompanyName = "Kolkota Knights", ContactName = "Sachin Tendulkar", ContactTitle = "current top cricket player", Address = "35 test row", City = "Kolkota", Region = "West Bengal", PostalCode = "3454534", Country = "India", Phone = "304923", Fax = "33325" }));
mockedReptryCtxt.Object.SaveChanges();
Later on in the code, I have the following excerpt of code where _context.Set() will return the In-Memory Data DBSet that we created previously
var query = _context.Set<TEntity>().AsQueryable();
if (typeof(TEntity).Name.Contains("Audit"))
{
return query;
}
if (includes != null && includes.Any())
{
foreach (var include in includes)
{
query = query.Include(include);
}
}
List<TEntity> resultsAsList = query.ToList(); // Error Thrown When using ToList()
var results = resultsAsList.AsQueryable();
When we invoke ToList(), it Throws the following Error:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=Collection was modified; enumeration operation may not execute.
Source=mscorlib
StackTrace:
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
at System.Collections.Generic.List`1.Enumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at BlahBlah.Framework.EntityFramework.EntityFrameworkRepository`1.ConcreteQuery(List`1 includes) in d:\EMIS\BlahBlah Framework\BlahBlahFrameworkLightweight\BlahBlah.Framework.EntityFramework\EntityFrameworkRepository.c s:line 51
at Castle.Proxies.EntityFrameworkRepository`1Proxy.ConcreteQuery_callback(List`1 includes)
at Castle.Proxies.Invocations.EntityFrameworkRepository`1_ConcreteQuery.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Moq.Proxy.CastleProxyFactory.CallContext.InvokeBase()
at Moq.InvokeBase.HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx)
at Moq.Interceptor.Intercept(ICallContext invocation)
at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.EntityFrameworkRepository`1Proxy.ConcreteQuery(List`1 includes)
at BlahBlah.Framework.Core.Repository.BaseRepository`1.Query(List`1 includes) in d:\EMIS\BlahBlah Framework\BlahBlahFrameworkLightweight\BlahBlah.Framework.Core\Repository\BaseRepository.cs:line 149
at Castle.Proxies.EntityFrameworkRepository`1Proxy.Query_callback(List`1 includes)
at Castle.Proxies.Invocations.IRepository`1_Query.InvokeMethodOnTarget()
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Moq.Proxy.CastleProxyFactory.CallContext.InvokeBase()
at Moq.InvokeBase.HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx)
at Moq.Interceptor.Intercept(ICallContext invocation)
at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation)
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.EntityFrameworkRepository`1Proxy.Query(List`1 includes)
at BlahBlah.Test.Unit.CntrlrsTests.CustomerControllerTest.Test_Creation_Of_Customer_Using_Constructor_Of _Customer_Controller_That_Expects_Arguments() in d:\EMIS\BlahBlah Framework\BlahBlahFrameworkLightweight\BlahBlah.Test.Unit\CntrlrsTests\CustomerControllerTest.cs:line 278
InnerException:
What steps do we need to take in order to stop the said error from being thrown( preferably without Changing too much of our Code Under Test)?
I had this problem as well but not iterating over the collection isn't really an option for me. After some thought, I did figure out a solution. The issue is that the mock sets up the various IQueryable properties off of a fixed IQueryable object from the original list. That causes any modification of that list to invalidate the corresponding IQueryable. The solution is to get a new IQueryable on each access using a lambda with Moq.
Here's the helper function I created to make mocking out DBSets easier, using the described technique.
public static Mock<DbSet<T>> MockDbSet<T>(List<T> list) where T : class
{
var mockSet = new Mock<DbSet<T>>();
mockSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(() => list.AsQueryable().Provider);
mockSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(() => list.AsQueryable().Expression);
mockSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(() => list.AsQueryable().ElementType);
mockSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => list.GetEnumerator());
mockSet.Setup(m => m.Add(It.IsAny<T>())).Callback((T x) => list.Add(x));
mockSet.Setup(m => m.AddRange(It.IsAny<IEnumerable<T>>())).Callback((IEnumerable<T> x) => list.AddRange(x));
mockSet.Setup(m => m.Remove(It.IsAny<T>())).Callback((T x) => list.Remove(x));
mockSet.Setup(m => m.RemoveRange(It.IsAny<IEnumerable<T>>())).Callback((IEnumerable<T> x) => list.RemoveAll(x.Contains));
return mockSet;
}
Edit: Added AddRange, Remove, RemoveRange since why not...
Edit 2: Correction for RemoveRange
I found a really Clumsy Solution:
List<TEntity> tempList = new List<TEntity>();
for (int i = query.Count() - 1; i >= 0; i--)
{
tempList.Add(query.ElementAt(i));
}
List<TEntity> resultsAsList = tempList.ToList();
var results = resultsAsList.AsQueryable();
In the aforementioned code, it is important to use a for loop with an index to go through the DBSet instance. Furthermore, in the loop, you add each element to a List. ( Basically, it's important to Avoid using the Iterator)
I have a noticication entity that has OneToMany relationship with its parameters, which is a list of NotificationParamEntity objects.
The code for both classes looks like:
// Notification Entity
#Entity
#Table (name = "NOTIFICATIONS")
public class NotificationEntity {
......
#OneToMany (mappedBy = "notification")
private List<NotificationParamEntity> params;
......
}
// Notification Parameter Entity
#Entity
#Table (name = "NOTIFICATIONS_PARAM")
public class NotificationParamEntity {
......
#Column (name = "KEY", length = 40, nullable = false)
#Enumerated (EnumType.STRING)
private NotificationParameterEnum key;
#Column (name = "VALUE", length = 4000, nullable = false)
private String value;
#ManyToOne
#JoinColumn (name = "NOTIFICATION_ID", nullable = false)
private NotificationEntity notification;
......
}
Now I can use the query below to get the notification that has a parameter named "P1" and with a value "V1":
SELECT DISTINCT anEntity FROM NotificationEntity anEntity, IN
(anEntity.params) p WHERE p.key = "P1" AND p.value = 'V1'
But when I want to find out the notification that has two specified parameters(P1=V1 and P2=V2), my query below failed to find anything:
SELECT DISTINCT anEntity FROM NotificationEntity anEntity, IN
(anEntity.params) p WHERE p.key = "P1" AND p.value = 'V1' AND p.key = "P2" AND p.value = "V2"
I can understand why it doesn't work: there is no parameter that can have two different keys, so the query return nothing.
But how to make this work? Assume I have a notification entity that has two parameters, one is named P1 and value is V1, the other one is P2 and the value is V2, how can I find this notification entity with JPQL query?
Try something like this:
SELECT n FROM NotificationEntity n WHERE EXISTS
(SELECT p FROM NotificationParamEntity p WHERE p.key = 'P1' AND p.value = 'V1'
AND p.notificationEntity = n)
AND EXISTS
(SELECT p2 FROM NotificationParamEntity p2 WHERE p2.key = 'P2' AND p2.value = 'V2'
AND p2.notificationEntity = n)
Note that it requires a reference from NotificationParamEntity to NotificationEntity (I don't see that column in the snippet of your code, but you should have it, a #ManyToOne).