Reputation: 35
I have a controller in SpringBoot that, when a request is made to an endpoint, calls a SpringBoot Service.
This service uses an entityManager to execute queries on the database. These queries can be of any type, both Select and Truncate, which would modify my database, and I don't want that.
I want my service to have read-only permissions. However, there are other services in my application that need write permissions, so changing the database user won't work for me.
An example code of the case I use would be:
@RestController
public class TestController {
@Autowired private TestService testService;
@GetMapping(value = “/test”, produces = "text/plain")
@ResponseBody
public ResponseEntity<String> getResponse(@RequestParam(value = “message”) String message) {
log.info("Received message: {}", message);
List<Map<String, Object>> response = testService.getResults(message);
// ad hoc code
}
}
Next, the service I want to have read only permissions over database (via entityManager)
@Service
public class TestService {
@Autowired client<?> client;
@PersistenceContext EntityManager entityManager;
public List<Map<String, Object>> getResults(String userMessage) {
final String query = client.generateQuery(userMessage).toLowerCase();
NativeQueryImpl nativeQuery = (NativeQueryImpl) entityManager.createNativeQuery(query);
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String, Object>> result = nativeQuery.getResultList();
return result;
}
}
Any other approach would be suitable as long as it achieves the desired result.
I tried to set the service as Transactional(readOnly = true), but it only affects on performance, not in service permissions:
/**
* A boolean flag that can be set to {@code true} if the transaction is
* effectively read-only, allowing for corresponding optimizations at runtime.
* <p>Defaults to {@code false}.
* <p>This just serves as a hint for the actual transaction subsystem;
* it will <i>not necessarily</i> cause failure of write access attempts.
* A transaction manager which cannot interpret the read-only hint will
* <i>not</i> throw an exception when asked for a read-only transaction
* but rather silently ignore the hint.
* @see org.springframework.transaction.interceptor.TransactionAttribute#isReadOnly()
* @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
*/
boolean readOnly() default false;
Upvotes: 2
Views: 239
Reputation: 16209
First thought: I think commonly this is not enforced. You can just write a service which only does reads, and inject it into your controller. After all, you are in control of your own code.
Looking further, you could split the repository from the service. You have both in one class in your example, usually they are separate. This would allow you to create a read-only repository for this purpose.
@Service
public class TestService {
@Autowired
private TestReadOnlyRepository testReadOnlyRepository;
public List<TestEntry> getResults(String userMessage) {
return testReadOnlyRepository.findById(userMessage);
}
Create an entity to use with the repository, instead of the List<Map<String, Object>>
@Entity
public class TestEntry {
@Id
@GeneratedValue
private String key;
private String value;
}
Note I did not use Object for the value, since I don't know what column type an Object would be in a database.
And for the repository itself we can lend from Baeldung to create a generic read-only Spring Data repository:
@NoRepositoryBean
public interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
}
public interface TestReadOnlyRepository extends ReadOnlyRepository<TestEntry , String> {
// we only use the methods offered by the ReadOnlyRepository
}
Upvotes: 2