Skip to content

Commit e889217

Browse files
committed
Performance Improvement via batchLoadViewsForAccountAccess
1 parent 924dab5 commit e889217

1 file changed

Lines changed: 69 additions & 35 deletions

File tree

obp-api/src/main/scala/code/views/MapperViews.scala

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,59 @@ object MapperViews extends Views with MdcLoggable {
4545
ViewDefinition.findCustomView(accountAccess.bank_id.get, accountAccess.account_id.get, accountAccess.view_id.get)
4646
}
4747
}
48+
49+
/**
50+
* Batch-load all views for a list of AccountAccess records in minimal DB queries,
51+
* instead of one query per AccountAccess row (N+1 problem).
52+
*
53+
* Returns a list of (AccountAccess, ViewDefinition) pairs for access records
54+
* that have a valid, private view.
55+
*/
56+
private def batchLoadViewsForAccountAccess(
57+
accountAccessList: List[AccountAccess]
58+
): List[(AccountAccess, ViewDefinition)] = {
59+
if (accountAccessList.isEmpty) return Nil
60+
61+
// 1. Separate system vs custom view access
62+
val (systemAccessList, customAccessList) = accountAccessList.partition(a => isValidSystemViewId(a.view_id.get))
63+
64+
// 2. Batch-load system views: one query per distinct view_id (small fixed set)
65+
val distinctSystemViewIds = systemAccessList.map(_.view_id.get).distinct
66+
val systemViewMap: Map[String, ViewDefinition] = distinctSystemViewIds
67+
.flatMap(vid => ViewDefinition.findSystemView(vid).toList.map(v => vid -> v))
68+
.toMap
69+
70+
val systemPairs: List[(AccountAccess, ViewDefinition)] = systemAccessList.flatMap { aa =>
71+
systemViewMap.get(aa.view_id.get).map { v =>
72+
// Clone and set bank_id/account_id per access record (system views don't store these)
73+
val viewWithContext = v.bank_id(aa.bank_id.get).account_id(aa.account_id.get)
74+
(aa, viewWithContext)
75+
}
76+
}
77+
78+
// 3. Batch-load custom views: one query using ByList on view_id, then filter in memory
79+
val customPairs: List[(AccountAccess, ViewDefinition)] = if (customAccessList.nonEmpty) {
80+
val distinctCustomViewIds = customAccessList.map(_.view_id.get).distinct
81+
val allCustomViews = ViewDefinition.findAll(
82+
By(ViewDefinition.isSystem_, false),
83+
ByList(ViewDefinition.view_id, distinctCustomViewIds)
84+
)
85+
// Index by (bank_id, account_id, view_id) for fast lookup
86+
val customViewMap: Map[(String, String, String), ViewDefinition] = allCustomViews
87+
.map(v => (v.bank_id.get, v.account_id.get, v.view_id.get) -> v)
88+
.toMap
89+
90+
customAccessList.flatMap { aa =>
91+
customViewMap.get((aa.bank_id.get, aa.account_id.get, aa.view_id.get)).map(v => (aa, v))
92+
}
93+
} else Nil
94+
95+
// 4. Combine and filter for private views only
96+
val allPairs = systemPairs ::: customPairs
97+
allPairs.filter { case (_, view) =>
98+
view.isPrivate || allowPublicViews
99+
}
100+
}
48101

49102
private def getViewsCommonPart(accountAccessList: List[AccountAccess]): List[View] = {
50103
//we need to get views from accountAccess
@@ -548,54 +601,34 @@ object MapperViews extends Views with MdcLoggable {
548601
}
549602

550603
def privateViewsUserCanAccess(user: User): (List[View], List[AccountAccess]) ={
551-
val accountAccess = AccountAccess.findAllByUserPrimaryKey(user.userPrimaryKey)
552-
.filter(accountAccess => {
553-
val view = getViewFromAccountAccess(accountAccess)
554-
view.isDefined && view.map(_.isPrivate)==Full(true)
555-
})
556-
val privateViews = accountAccess.map(getViewFromAccountAccess).flatten.distinct
557-
(privateViews, accountAccess)
604+
val allAccess = AccountAccess.findAllByUserPrimaryKey(user.userPrimaryKey)
605+
val pairs = batchLoadViewsForAccountAccess(allAccess)
606+
(pairs.map(_._2).distinct, pairs.map(_._1))
558607
}
559608
def privateViewsUserCanAccess(user: User, viewIds: List[ViewId]): (List[View], List[AccountAccess]) ={
560-
val accountAccess = AccountAccess.findAll(
609+
val allAccess = AccountAccess.findAll(
561610
By(AccountAccess.user_fk, user.userPrimaryKey.value),
562611
ByList(AccountAccess.view_id, viewIds.map(_.value))
563-
).filter(accountAccess => {
564-
val view = getViewFromAccountAccess(accountAccess)
565-
view.isDefined && view.map(_.isPrivate) == Full(true)
566-
})
567-
PrivateViewsUserCanAccessCommon(accountAccess)
612+
)
613+
val pairs = batchLoadViewsForAccountAccess(allAccess)
614+
(pairs.map(_._2), pairs.map(_._1))
568615
}
569616
def privateViewsUserCanAccessAtBank(user: User, bankId: BankId): (List[View], List[AccountAccess]) ={
570-
val accountAccess = AccountAccess.findAll(
617+
val allAccess = AccountAccess.findAll(
571618
By(AccountAccess.user_fk, user.userPrimaryKey.value),
572619
By(AccountAccess.bank_id, bankId.value)
573-
).filter(accountAccess => {
574-
val view = getViewFromAccountAccess(accountAccess)
575-
view.isDefined && view.map(_.isPrivate) == Full(true)
576-
})
577-
PrivateViewsUserCanAccessCommon(accountAccess)
620+
)
621+
val pairs = batchLoadViewsForAccountAccess(allAccess)
622+
(pairs.map(_._2), pairs.map(_._1))
578623
}
579624
def getAccountAccessAtBankThroughView(user: User, bankId: BankId, viewId: ViewId): (List[View], List[AccountAccess]) ={
580-
val accountAccess = AccountAccess.findAll(
625+
val allAccess = AccountAccess.findAll(
581626
By(AccountAccess.user_fk, user.userPrimaryKey.value),
582627
By(AccountAccess.bank_id, bankId.value),
583628
By(AccountAccess.view_id, viewId.value)
584-
).filter(accountAccess => {
585-
val view = getViewFromAccountAccess(accountAccess)
586-
view.isDefined && view.map(_.isPrivate) == Full(true)
587-
})
588-
PrivateViewsUserCanAccessCommon(accountAccess)
589-
}
590-
591-
private def PrivateViewsUserCanAccessCommon(accountAccess: List[AccountAccess]): (List[ViewDefinition], List[AccountAccess]) = {
592-
val listOfTuples: List[(AccountAccess, Box[ViewDefinition])] = accountAccess.map(
593-
accountAccess => (accountAccess, getViewFromAccountAccess(accountAccess))
594-
)
595-
val privateViews = listOfTuples.flatMap(
596-
tuple => tuple._2.map(v => v.bank_id(tuple._1.bank_id.get).account_id(tuple._1.account_id.get))
597629
)
598-
(privateViews, accountAccess)
630+
val pairs = batchLoadViewsForAccountAccess(allAccess)
631+
(pairs.map(_._2), pairs.map(_._1))
599632
}
600633

601634
def privateViewsUserCanAccessForAccount(user: User, bankIdAccountId : BankIdAccountId) : List[View] = {
@@ -604,7 +637,8 @@ object MapperViews extends Views with MdcLoggable {
604637
bankIdAccountId.accountId,
605638
user.userPrimaryKey
606639
)
607-
accountAccess.map(getViewFromAccountAccess).flatten.filter(view => view.isPrivate == true).distinct
640+
val pairs = batchLoadViewsForAccountAccess(accountAccess)
641+
pairs.map(_._2).distinct
608642
}
609643

610644

0 commit comments

Comments
 (0)