@@ -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