Ok, so, thanks to @joanolo I’ve got a pretty good solution. 1. **Create a session variable which holds the accountGroupId.** Spring @Transactional & Hibernate isolate DB sessions on a per thread basis, so there’s no risk of concurrent parameter bleed-through. 2. **Create DB stored functions which take in a value and store it in the session variable.** Although, if you wanted to, you could just run an update query `SET @mySessionVar = 101;` instead of creating a set() function, but you **MUST** have a get function. MySQL won't let you put a session variable in a view definition. 3. **Create restricted views which compare against the session var in the where clause.** Since this variable is an account-wide single value (& won’t be changing from select to select within the same user), this works fine. Each request/user will have their own DB session & their own instance of the session var; all completely isolated from each other. They just call the set() function to initialize their session var before running their queries / inserts, and everything works perfectly. **Now, speaking of inserts:** this is where it gets sticky. There is an easy solution (very simple triggers), the problem is DB support. I have [another stack exchange question](https://dba.stackexchange.com/q/159231/113633) right now, trying to finalize the issue, but as it looks right now: there is an old bug in MySQL v5.6 (& in MariaDB) where they were checking NULL constraints before running triggers. This prevented using triggers to populate these fields. However, this bug was fixed in MySQL v5.7 (& claimed to be fixed in MariaDB v10.1.6, but it doesn’t appear to be). If you use MySQL v5.7+, or any other DB which complies with the SQL standard, and runs triggers before checking NULL constraints, you’ll be fine. Here’s a simple code example implementing the fix, and I also made a [SQL Fiddle](http://sqlfiddle.com/#!9/1779f64/1): CREATE TABLE privateNotes ( id int(11) NOT NULL, userId int(11) NOT NULL, text varchar(500) NOT NULL, groupId int(11) NOT NULL ); INSERT INTO privateNotes (id, userId, text, groupId) VALUES (30, 1, '[John] A Secret Message', 101), (32, 2, '[George] A Secret Message', 101), (34, 3, '[Sarah] A Secret Message', 202), (36, 4, '[Mary] A Secret Message', 202); -- -------------------------------------------------------- -- Get CREATE FUNCTION getCurrentGroupId() RETURNS int(11) NO SQL BEGIN RETURN @currentGroupId; END // -- Set CREATE FUNCTION setCurrentGroupId(groupId_in int(11)) RETURNS int(11) NO SQL BEGIN SET @currentGroupId = groupId_in; RETURN @currentGroupId; END // -- -------------------------------------------------------- CREATE VIEW myPrivateNotes AS SELECT privateNotes.id AS id, privateNotes.userId AS userId, privateNotes.text AS text FROM privateNotes WHERE privateNotes.groupId = getCurrentGroupId(); /* 101, 202, etc. */ -- -------------------------------------------------------- -- Trigger CREATE TRIGGER before_insert_privateNotes BEFORE INSERT ON privateNotes FOR EACH ROW BEGIN IF NEW.groupId IS NULL THEN SET NEW.groupId = getCurrentGroupId(); END IF; END // -- -------------------------------------------------------- -- ------- -- -- TESTS -- To see resultSets, run selects in the SQL box over there -> -- ------- -- Can't run inserts over there -> -- View Insert Test SELECT setCurrentGroupId(999); // -- (Uncomment) Error: Field 'groupId' doesn't have a default value -- INSERT INTO privateNotes (id, userId, text) VALUES (50, 51, 'a cool message'); // -- View Select Tests -- -- Copy the full chunk bellow to the Sql box over there -> -- Don't forget to change the delimeter to: [//] SELECT setCurrentGroupId(NULL); // SELECT * from myPrivateNotes; -- Returns Zero Results [Correct] // SELECT setCurrentGroupId(101); // SELECT * from myPrivateNotes; -- Returns Valid Results [Correct] //