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.
Related
I have following code
#Transactional(readOnly = true)
override fun getProjects(
pageNum: Int,
pageSize: Int,
sortBy: List<com.fmetric.project_mgmt_api.util.Sort>,
filter: ProjectFilterDescriptor,
full: Boolean
): PageDescriptor<ProjectDescriptor> {
val orders = mutableListOf<Sort.Order>()
sortBy.forEach { sort -> orders.add(Sort.Order(map(sort.sortOrder), sort.sortBy)) }
val sort = Sort.by(orders)
val page = PageRequest.of(pageNum, pageSize, sort)
val spec = buildSpecification(filter)
val projectsPage = projectRepository.findAll(spec, page)
return Page(
projectsPage.number,
projectsPage.size,
projectsPage.content.map { p -> map(p, full) },
projectsPage.totalElements,
map(projectsPage.sort)
)
}
...
interface ProjectRepository : JpaSpecRepository<Project>, PagingAndSortingRepository<Project, UUID?> {
...
#PreAuthorize("addPermissionFilter(#spec, 'Project', 'View')")
override fun findAll(spec: Specification<Project>?, pageable: Pageable): Page<Project>
...
package org.springframework.data.jpa.repository
public interface JpaSpecificationExecutor<T> {
...
Page<T> findAll(#Nullable Specification<T> var1, Pageable var2);
...
it generates following hibernate log with six HSQL queries, two of them are duplicates, why?
https://gist.github.com/iva-nova-e-katerina/b336f34b666cbb69c26f682b378ca311
UPD: this is the classical "Hibernate N+1 problem" and the best working solution is described here https://medium.com/geekculture/resolve-hibernate-n-1-problem-f0e049e689ab . Problem is solved.
i have one List (growable) with an item (actually item 0:
items is of class Team
items[_id = 1, _team = "Team01", _note = "blabla"]
and I want to transfer it into another list with a different structure:
participants is of class User
participants[id = 1, name = "participant1"]
skipping the note and translating _id into id and so on.So at last the result would give me
participants[id = 1, name = "team01"]
(sorry for the writing, I describe it out of the debugger)
i tried something like this, but doesnt work with value:
List<TestTeam> participants;
for (var value in items) {
participants.add(new TestTeam(value.id, value.team));
}
my class Team is defined like this:
class Team {
int _id;
String _team;
String _note;
Team(this._team, this._note);
Team.map(dynamic obj) {
this._id = obj['id'];
this._team = obj['team'];
this._note = obj['note'];
}
int get id => _id;
String get team => _team;
String get note => _note;
Map<String, dynamic> toMap() {
var map = new Map<String, dynamic>();
if (_id != null) {
map['id'] = _id;
}
map['team'] = _team;
map['note'] = _note;
return map;
}
Team.fromMap(Map<String, dynamic> map) {
this._id = map['id'];
this._team = map['team'];
this._note = map['note'];
}
}
You should implement below way
void main() {
List<Team> teams=[];
List<User> participants=[];
for (var i = 0; i < 4; i++) {
teams.add(Team(i,'Team_$i','Note_$i'));
}
for (var value in teams){
participants.add(User(value.id,value.team));
}
for (var value in teams){
print(value.toString());
}
for (var value in participants){
print(value.toString());
}
}
class Team{
int id;
String team;
String note;
Team(this.id,this.team,this.note);
toString()=> 'Team Map :{id:$id,team:$team,note:$note}';
}
class User{
int id;
String team;
User(this.id,this.team);
toString()=> 'User Map :{id:$id,team:$team}';
}
Output
Team Map :{id:0,team:Team_0,note:Note_0}
Team Map :{id:1,team:Team_1,note:Note_1}
Team Map :{id:2,team:Team_2,note:Note_2}
Team Map :{id:3,team:Team_3,note:Note_3}
User Map :{id:0,team:Team_0}
User Map :{id:1,team:Team_1}
User Map :{id:2,team:Team_2}
User Map :{id:3,team:Team_3}
We regularily write extension methods like this that convert from Database objects to DTO objects for use elsewhere in our system.
As you can see in the example below, the actual mapping code is repeated. Is it possible to write a reusable select mapping that can be used in both of these methods?
public static async Task<List<Group>> ToCommonListAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
var groups =
await entityGroups.Select(
g =>
new Group()
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
}).ToListAsync();
return groups;
}
public static async Task<Group> ToCommonFirstAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
var group =
await entityGroups.Select(
g =>
new Group()
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
}).FirstOrDefaultAsync();
return group;
}
You could move your mapping/projection code out into a variable like this:
public static class Extensions
{
private static readonly Expression<Func<DataLayer.Models.Group, Group>> Projection = g =>
new Group
{
Id = g.Id,
AccountId = g.AccountId,
Name = g.Name,
ParentId = g.ParentId,
UserIds = g.GroupUserMappings.Select(d => d.UserId).ToList()
};
public static async Task<List<Group>> ToCommonListAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
return await entityGroups.Select(Projection).ToListAsync();
}
public static async Task<Group> ToCommonFirstAsync(this IQueryable<DataLayer.Models.Group> entityGroups)
{
return await entityGroups.Select(Projection).FirstOrDefaultAsync();
}
}
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 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?