Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreSpring Data Release Evans has been around for a while and it's more than time to finally introduce you to the latest and greatest features we shipped with it.
There's a lot to cover since major enhancements have gone into the commons module. Those changes already have made it into some store modules and will go on and sneak their way into others over time, too. All of them are already available for at least Spring Data JPA. That said, lets jump right in.
Java8 has now been around for a while and previous Spring Data release trains already added fundamental support for some of those. With the Evans release train we extended the support significantly.
Java 8's Optional
has been a supported return since the Dijkstra release freeing you of having null
checks spread across your code. We simply wrap and unwrap values for you when used as return types with repositories.
As of the Evans release default methods can be used in repository interfaces to e.g. forward parts of the parameters handed into the method to other query methods.
interface PersonRepository extends Repository<Person, Long> {
Optional<Customer> findByLastname(String lastname);
default Optional<Customer> findByLastname(Customer customer) {
return findByLastname(customer == null ? null : customer.getLastname());
}
}
Configuring your application to make use of different Spring Data modules has not been without issues so far. E.g. you might want to combine JPA and MongoDB where Customer
happens to be a JPA Entity while Order
is a MongoDB Document both persisted via according repository interfaces.
@Entity
class Customer {
@Id @GeneratedValue Long id;
String firstname, lastname;
// ...
}
@Document
class Order {
@Id String id;
Long customerId;
Date orderDate;
// ...
}
interface CustomerRepository extends CrudRepository<Customer, Long> {}
interface OrderRepository extends CrudRepository<Order, String> {}
Until the Spring Data Evans release you had to manually configure the repository setup for MongoDB and JPA to mutually exclude the interfaces not relevant for the given store. Users usually used separate packages for that.
Now the repository setup detects that multiple Spring Data modules are on the classpath, and automatically restricts the repository scanning and inspect the domain type used by a given repository for store specific annotations such as @Entity
and @Document
to determine the concrete implementation they belong to. E.g. the Spring Data MongoDB module would drop the (accidentally) detected CustomerRepository
as we don't find an @Document
annotation.
Dynamically limiting results is no new concept since Spring Data has had Pagable
as abstraction since its inception and I bet nearly every Spring Data user is already familiar with something like this:
List<Person> findByLastname(String lastname, Pageable page)
This method declaration provides quite some flexibility: clients define the page number, size and a sort order of the elements they want to access. This is great if these values change dynamically (e.g. when you traverse the result set page by page).
But what if you're always only interested in e.g. the first 10 results and you always want them to be ordered by lastname? This could've been achieved by statically defining a PageRequest
and reusing that for every method invocation. However, that still required the client to hand in the special PageRequest
.
As of Spring Data Evans we now offer you a convenient way to explicitly limit the result set to a certain number of elements by using the keywords top
and first
followed by an optional positive numeric value (defaulting to 1).
List<Person> findTop10ByLastnameOrderByFirstnameDesc(String lastname);
The Evans RC1 release introduced basic text index support for MongoDB 2.6. Using @TextIndexed
allows you to mark properties you want to have text search enabled for so that we can go on and create the index for you. Note, that placing @TextIndexed
on properties referring to complex types will index all properties of that type. Since scoring is a fundamental part of full text search the @TextScore
annotation will assert that any full text query returns the documents score allowing you to order them by relevance.
@Document
class BlogPost {
@Id String id;
@TextIndexed(weight = 3) String title;
@TextIndexed(weight = 2) String content;
@TextIndexed List<String> categories;
@TextScore Float score;
}
That in place, we extended the repository support to accept a TextCriteria
instance that will define detailed options about the text search that shall be executed: the terms to be searched for, language options etc.
interface BlogPostRepository extends CrudRepository<BlogPost, String> {
Page<BlogPost> findBy(TextCriteria criteria, Pageable page);
List<BlogPost> findAllByOrderByScoreDesc(TextCriteria criteria);
}
The first query method is quite straight forward. It executes the given TextCriteria
and pages the results. The second query method definition combines the given TextCriteria
with a standard criteria definition derived from the method name. This shows that you can freely combine text search with standard query easily.
We added @Meta
allowing you to define output and behavior of a query. By setting e.g. maxExecutionTime
one can define the maximum duration a query may take (in milliseconds). Any execution that exceeds the limit will result in an error. You can also advice MongoDB to only scan through a maximum number of documents and return what has been found until reaching the limit by setting maxScanDocuments
, while comment
allows you to define text you can search for within the system.profile
collection in case you got profiling enabled for your MongoDB instance.
@Meta(maxExcecutionTime = 100, comment = "onlyLimitedTime")
List<Customer> findByFirstname(String firstname);
Redis 2.8 introduced high-availability support know as Sentinels. The Redis module of Spring Data Evans adds support to easily configure connecting to a sentinel setup so that your client will be able to continue working in case of re-elections of master nodes in a Redis cluster.
RedisSentinelConfiguration
defines where the Sentiels are located so that the ConnectionFactory
can set up pooling accordingly. In case of Jedis it will create a JedisSentinelPool
for automatic failover. This means that in case your master node goes down, you'll receive, as soon as the Sentinels agreed on a new master, a connection to the new master without the need of any further interaction.
@Configuration
class RedisSentinelApplicationConfig {
@Bean
RedisConnectionFactory connectionFactory() {
return new JedisConnectionFactory(sentinelConfig());
}
@Bean
RedisSentinelConfiguration sentinelConfig() {
return new RedisSentinelConfiguration().master("mymaster")
.sentinel("localhost", 26379)
.sentinel("localhost", 26380)
.sentinel("localhost", 26381);
}
}
The upcoming Spring Boot 1.2, will even take this even further by automatically picking up the RedisSentinelConfiguration
if present and initialize the RedisConnectionFactory
accordingly.
Although the Solr Schema API is not finished yet, we already try to support as much of it as possible. With the Evans release you can now dynamically add missing fields to an existing (managed) schema. To achieve this, we read the existing field definition and compare it against the one derived from the properties of the domain type. To do so we extended the @Indexed
annotation a bit. It now allows some fine tuning of the fields to be created as values such as indexed
, stored
and solrType
can be explicitly defined.
@Configuration
@EnableSolrRepositories(schemaCreationSupport = true)
class SolrConfiguration {
@Bean
SolrServer solrServer() {
return new HttpSolrServer("http://localhost:8983/solr");
}
}
@SolrDocument(solrCoreName = "collection1")
class ManagedProduct {
@Id String id;
@Indexed(type = "text_general") String name;
@Indexed(name = "cat") List<String> category;
}
As always we are eager to hear from you! Reach out to us on Twitter, Stackoverflow or JIRA to request new features, suggest improvements or report a bug.