1

Problem

I am developing a spring-boot application that needs to read data from an oracle database.
The database itself is outside of my control and I cannot change the way it's structured.
I'm using spring-data-jpa in combination with hibernate to interact with the database.
I have the following entity:

@Entity
@Table(name = "mytable")
public class MyEntity {
    @Id
    @Column
    private String entityId;

    // This column might have a value like "abc,xyz,a1b" in the db
    @Column
    private String associatedIds;
}

The column associatedIds contains comma separated values (eg. abc,xyz,a1b).
Users of my application only have permission to read rows with atleast 1 associatedId they have access to. The Ids they have access to are represented as a List<String> allowedIds.

In order to ensure that users only receive the data they are allowed to, I need to restrict my queries. I'm having trouble constructing a query that meets my requirements.
If users only had access to a single Id I could simply use the following query using a simple LIKE operator:

@Query(value = "SELECT e FROM MyEntity e WHERE e.associatedIds LIKE '%:allowedId%'")
Page<MyEntity> findAllByAllowedId(@Param("allowedId") String allowedId, Pageable pageable);

That query isn't sufficient for my usecase because users have access to more than one Id.

I came up with a few ways to approach this problem, but didn't manage to fully implement any of them.

Approach 1: "IN LIKE"

The first approach would be a query like this:

@Query(value = "SELECT e FROM MyEntity e WHERE e.associatedIds IN (LIKE '%:allowedIds%')")
Page<MyEntity> findAllByAllowedIds(@Param("allowedIds") List<String> allowedIds, Pageable pageable);

This doesn't work because as far as I know there is no query combining IN and LIKE.

Approach 2: "AttributeConverter"

The next thing I tried was to convert the comma separated string to a List<String> using the following converter:

@Converter
public class StringArrayToStringConverter implements AttributeConverter<List<String>, String> {
    @Override
    public String convertToDatabaseColumn(List<String> strings) {
        return strings == null ? null : StringUtils.join(strings, ',');
    }

    @Override
    public List<String> convertToEntityAttribute(String s) {
        if(StringUtils.isBlank(s)) {
            return Collections.emptyList();
        }
        return Arrays.asList(s.split(","));
    }
}

This conversion works, but doesn't allow me to construct queries as if the column were a list, because the database remains unchanged.

Approach 3: "@Formula"

Another possible solution might be hibernate's @Formula annotation. There might be a way to create a virtual column that can be searched according to my requirements. I have no clue where to start with that though, as I've never used that annotation before and don't quite understand how list types work in oracle. Another thing to consider is whether you can actually perform hql queries on formula columns.

Approach 4: "Multiple OR's"

The last possible solution I could come up with would involve adding a separate where clause for each allowed Id, this could end up looking like:

SELECT e FROM MyEntity e WHERE e.allowedIds LIKE'%allowedIds.get(0)%' OR e.allowedIds LIKE'%allowedIds.get(1)%' etc..

With this approach I also don't know how I could implement that in spring-data-jpa.

I would greatly appreciate some guidance on how to implement these solutions or suggestions I didn't think of.

1 Answers1

0

How about using regular expressions function regexp_substr:

@Query(value = "SELECT e FROM MyEntity e WHERE e.associatedIds IN ( SELECT regexp_substr(:allowedId,'[^,]+', 1, level) FROM dual connect by regexp_substr(:allowedId, '[^,]+', 1, level) IS NOT NULL \n#pageable\n" ,nativeQuery=true )
Page<MyEntity> findAllByAllowedId(@Param("allowedId") String allowedId, Pageable pageable);
Mansoor
  • 1,157
  • 10
  • 29
  • After testing that solution it appears that it compares the column `associatedIds` with each entry in the parameter `allowedIds`. The problem with that is that the column `associatedIds` itself is a comma separated list of ids. I couldn't manage to find any rows that had multiple ids in `associatedIds`. – ElectronicManuel Jan 28 '20 at 18:14
  • It's hard to understand what you meant. Could you explain in simple words? :) – Mansoor Jan 29 '20 at 10:30
  • The database column I want to search through contains values like `"abc"` or values like `"abc,xyz,lmn"`. In my java code I have a list of values, for example `["abc", "aaa", "qqq"]`. I want to get every row where the database column contains atleast one of those values from my java code. As an example the row `"abc,ccc"` should match if I enter `["abc", "bbb"]`. it should also be returned if I enter `["xyz", "ccc"]`. – ElectronicManuel Jan 29 '20 at 13:32
  • Can you do a work around by converting list to String? Assuming you've a list as described as listOfIds `String allowedId=StringUtils.join(listOfIds,",");` And use it in the above query. – Mansoor Jan 29 '20 at 15:52
  • I can totally do that, that's what I did when testing your select statement. I first compared the db column `ccc` with `abc,ccc` and it did match. However when I compared the db column `"abc,ccc"` with `"abc,bbb"` it did not match. I think it ended up comparing `abc` with the entire `"abc,ccc"` and then `bbb` with `abc,ccc`. – ElectronicManuel Jan 29 '20 at 17:39