Djolar
is a protocol like graphql, but only focus on how to filter data in database. It is very
common to use multiple filter in web-app or mobile-app, Djolar
is lifesaver to build clean API
services, unified frontend component.
For example, we need to search a user with
name
equal toenix
,age
greater than18
so we can invoke the api with the following query parameters:
http://localhost:8000/api/v1/users?q=name__eq__enix|age__gt__18
q
stand for query
, the query value syntax as below:
q-value := <filter-clause1>[|<filter-clause2>|...]
filter-clause := <field-name>__<operator>__<filter-value>
support operators:
operator | stands for |
---|---|
eq | equal (=) |
gt | greater than (>) |
ge | greater than or equal (>=) |
lt | less than (<) |
le | less than or equal (<=) |
co | contain (LIKE) |
sw | starts with (LIKE) |
ew | ends with (LIKE) |
in | in (IN) |
nn | is not null |
nu | is null |
jo | json overlaps, test if given field have any of the value in target list, eg., tags__jo__1,2, test tags contains 1 or 2 |
jc | json contains, test if given field contains all the values in the target list. eg., tags__jc__1,2, test tags contains 1 and 2 |
<dependency>
<groupId>com.enixyu</groupId>
<artifactId>djolar-mybatis</artifactId>
<version>${latest.version}</version>
</dependency>
-
Setup interceptor for mybatis
<plugins> <!-- other interceptors --> ... <plugin interceptor="com.enixyu.djolar.mybatis.plugin.DjolarInterceptor"> <!-- dialect for build sql query --> <property name="dialect" value="mysql=com.enixyu.djolar.mybatis.dialect.MySQLDialect"/> <!-- throw exception if field not found in mapping, possible values: true, false --> <property name="throw-if-field-not-found" value="true"/> <!-- throw exception if operator not supported, possible values: true, false --> <property name="throw-if-operator-not-support" value="true"/> </plugin> </plugins>
-
Define mapper interface
@Mapper // Specify the mapping class @Mapping(Blog.class) public interface BlogMapper { // DjolarInterceptor only handle method with QueryRequest as parameter List<Blog> findAll(QueryRequest request); Blog findById(int id); // Override the mapping class defined in class level @Mapping(BlogDjolarMapping.class) List<Blog> findBlogWithUser(QueryRequest request); }
-
Define the mapper xml
<mapper namespace="com.enixyu.djolar.mybatis.mapper.BlogMapper"> ... <select id="findAll" resultType="com.enixyu.djolar.mybatis.domain.Blog"> SELECT * FROM `blog` </select> <select id="findBlogWithUser" resultType="com.enixyu.djolar.mybatis.domain.Blog"> SELECT `blog`.* FROM `blog` INNER JOIN `user` ON `blog`.`user_id` = `user`.`id` </select> ... </mapper>
-
Client make query
public class Test { @Test public void testDjolarIntegerEqual() { try (SqlSession session = this.sessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); QueryRequest request = new QueryRequest(); // Get the records with `id` column equal to `1` request.setQuery("id__eq__1"); List<Blog> results = mapper.findAll(request); Assertions.assertEquals(1, results.size()); } } }
- IN values missing when mapper xml sql using
IN
operator.
- Add new operator
jo
(json overlaps) andjc
(json contains). - Refactor the operator specific value parsed logic into dialect.
- Handle invalid expression value when parsing expression, throw exception if
flag
KEY_THROW_IF_EXPRESSION_INVALID
is on.
- Ignore query with empty string, without throwing expression invalid exception.
- Return a false where clause (
1 = 0
) for invalid operator, invalid expression, mapping field not found if plugin parameters are set tofalse
. - Fix sort clause without using the
tableName
issue. - Add more test cases.
- Add a plugin parameter
throw-if-expression-invalid
to control whether throw exception if expression is invalid. - Set default value of all plugin parameters to
false
(Since expression is typically passed from frontend, the backend does not know the expression in advance).
- Fix interceptor crash when
QueryRequest
parameter is null issue.
- Support mapper method with multiple parameters including
QueryRequest
parameter.
- Add plugin property (
throw-if-field-not-found
) for user to decide whether throw exception if query field not found. - Add plugin property (
throw-if-operator-not-support
) for user to decide whether throw exception if operator not supported.
- Support
QueryRequest
subclass as mapper parameter. - Remove multiple parameters support.
- Fix no-parameter mapper method crash issue.
- Support customize
dialect
class. Support mapper method with multiple parameters includingQueryRequest
parameter.
- Throw
DjolarParserException
if sort field not found in query mapping.
- Throw
DjolarParserException
if query field not found in query mapping - Throw
DjolarParserException
if operator is not supported
- Fix parser failed to parse value when mapping class using boxed type
- Reorganize the benchmark codes
- Upgrade maven version
-
Support
in
andnot in
operatorspublic class Test { @Test void testCollectionIn() { try (SqlSession session = this.sessionFactory.openSession()) { BlogMapper mapper = session.getMapper(BlogMapper.class); QueryRequest request = new QueryRequest(); // in operator request.setQuery("id__in__1,2,3"); List<Blog> results = mapper.findAll(request); Assertions.assertEquals(2, results.size()); // not in operator request.setQuery("id__ni__1,2,3"); List<Blog> results = mapper.findAll(request); Assertions.assertEquals(2, results.size()); } } }
-
Fix issue when query with multiple same db field, e.g., two filter statement with
id
field int the following query clauseid__gt__1|n__sw__abc|id__lt__11
- add
is null
andis not null
operatorspublic class Test { void test() { // filter with blog name is not null QueryRequest request = new QueryRequest(); request.setQuery("blog_name__nn"); List<Blog> results = mapper.findBlogWithUser(request); // filter with blog name is null request.setQuery("blog_name__nu"); List<Blog> results = mapper.findBlogWithUser(request); } }
- support additional filter and sort in mapper method
@Mapper @Mapping(Blog.class) public interface BlogMapper { // force to filter user_id equal to 1 @AdditionalWhere(where = "user_id__eq__1") // force to sort by "id" desc and "name" asc @AdditionalSort(sort = "-id,name") List<Blog> findMyBlogs(QueryRequest request); }
- add request field builder
- implement djolar spec