It is possible to provide custom implementations of methods in a Spring Data JPA repository, which enables complete control on queries and return types. The approach is as follows:
- Define an interface with the desired method signatures.
- Implement the interface to achieve the desired behavior.
- Have the Repository extend both
JpaRepository
and the custom interface.
Here is a working example that uses JpaRepository
, assuming a user_table
with two columns, user_id
and user_name
.
UserEntity class in model package:
@Entity
@Table(name = "user_table")
public class UserEntity {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name = "user_id")
private Long userId;
@Column(name = "user_name")
private String userName;
protected UserEntity() {}
public UserEntity(String userName) {
this.userName = userName;
// standard getters and setters
}
Define interface for the custom repository in the repository package:
public interface UserCustomRepository {
List<String> findUserNames();
}
Provide implementation class for the custom interface in the repository package:
public class UserCustomRepositoryImpl implements UserCustomRepository {
// Spring auto configures a DataSource and JdbcTemplate
// based on the application.properties file. We can use
// autowiring to get a reference to it.
JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// Now our custom implementation can use the JdbcTemplate
// to perform JPQL queries and return basic datatypes.
@Override
public List<String> findUserNames() throws DataAccessException {
String sql = "SELECT user_name FROM user_table";
return jdbcTemplate.queryForList(sql, String.class);
}
}
Finally, we just need to have the UserRepository
extend both JpaRepository
and the custom interface we just implemented.
public interface UserRepository extends JpaRepository<UserEntity, Long>, UserCustomRepository {}
Simple test class with junit 5 (assuming the database is initially empty):
@SpringBootTest
class UserRepositoryTest {
private static final String JANE = "Jane";
private static final String JOE = "Joe";
@Autowired
UserRepository repo;
@Test
void shouldFindUserNames() {
UserEntity jane = new UserEntity(JANE);
UserEntity joe = new UserEntity(JOE);
repo.saveAndFlush(jane);
repo.saveAndFlush(joe);
List<UserEntity> users = repo.findAll();
assertEquals(2, users.size());
List<String> names = repo.findUserNames();
assertEquals(2, names.size());
assertTrue(names.contains(JANE));
assertTrue(names.contains(JOE));
}
}