Apache Maven
<dependency>
<groupId>org.fastquery</groupId>
<artifactId>fastquery</artifactId>
<version>1.0.140</version> <!-- fastquery.version -->
</dependency>
Gradle/Grails
compile 'org.fastquery:fastquery:1.0.140'
FastQuery основан на языке Java и упрощает работу с данными на уровне Java. Он предоставляет небольшое количество аннотаций, так что пользователям нужно только понимать их значение. Это делает ядро фреймворка легко перестраиваемым и способным к непрерывному развитию.
where
с помощью аннотации @Condition
.@Source
, что особенно полезно в мультитенантных системах, где требуется изоляция структур баз данных друг от друга.@QueryByNamed
, которые позволяют использовать динамические шаблоны SQL.JRE 8+
Файлы конфигурации по умолчанию ищутся в каталоге classpath
. Можно настроить другое местоположение, например:
System.setProperty("fastquery.config.dir","/data/fastquery/configs");
Это переопределит файлы конфигурации в каталоге classpath
. Если проект запускается как jar-файл, можно указать каталог конфигурации с помощью параметра -D
команды java -jar
:
java -jar Start.jar -Dfastquery.config.dir=/data/fastquery/configs
Поддерживает все настройки c3p0, доступные на официальном сайте.
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="xk-c3p0">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://192.168.1.1:3306/xk?useSSL=false</property>
<property name="user">xk</property>
<property name="password">abc123</property>
<property name="acquireIncrement">50</property>
<property name="initialPoolSize">100</property>
<property name="minPoolSize">50</property>
<property name="maxPoolSize">1000</property>
<property name="maxStatements">0</property>
<property name="maxStatementsPerConnection">5</property>
</named-config>
<!-- 可以配置多个named-config节点,多个数据源 -->
<named-config name="name-x"> ... ... </named-config>
</c3p0-config>
Используется для настройки пула соединений Druid. Подробные инструкции доступны на GitHub.
<beans>
<bean name="xkdb1" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://db.fastquery.org:3305/xk" />
<property name="username" value="xk" />
<property name="password" value="abc123" />
<property name="filters" value="stat" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
</bean>
<!-- 再配置一个数据源 -->
<bean name="xkdb2" id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://db.fastquery.org:3305/xk" />
<property name="username" value="xk" />
<property name="password" value="abc123" />
</bean>
</beans>
Настраивает пул соединений HikariCP. Подробные параметры доступны на GitHub. Для лучшей производительности рекомендуется следующая конфигурация при работе с MySQL.
<beans>
<bean name="xkdb2">
<property name="jdbcUrl" value="jdbc:mysql://192.168.1.1:3306/xk" />
<property name="dataSource.user" value="xk" />
<property name="dataSource.password" value="abc123" />
<property name="dataSource.cachePrepStmts" value="true" />
<property name="dataSource.prepStmtCacheSize" value="250" />
<property
``` ```
name="dataSource.prepStmtCacheSqlLimit" value="2048" />
<property name="dataSource.useServerPrepStmts" value="true" />
<property name="dataSource.useLocalSessionState" value="true" />
<property name="dataSource.rewriteBatchedStatements" value="false" />
<property name="dataSource.cacheResultSetMetadata" value="true" />
<property name="dataSource.cacheServerConfiguration" value="true" />
<property name="dataSource.elideSetAutoCommits" value="true" />
<property name="dataSource.maintainTimeStats" value="false" />
</bean>
<!-- 可以配置多个bean节点,提供多个数据源 -->
<bean name="name-x"> ... ... </bean>
Поддержка нескольких пулов соединений, например, одновременная работа Druid и HikariCP, а также настройка нескольких источников данных.
Конфигурация области действия источника данных
// @author xixifeng (fastquery@126.com)
// Конфигурация должна соответствовать стандартной синтаксису JSON.
{
"scope":[
// config используется для указания того, кто предоставляет источник данных, например,"c3p0","druid","hikari" и т.д.
{
"config": "c3p0", // Указывает, что c3p0 отвечает за предоставление источника данных
"dataSourceName": "xk-c3p0", // Имя источника данных
"basePackages": [ // Область действия этого источника данных
"org.fastquery.example", // Адрес пакета
"org.fastquery.dao.UserInfoDBService" // Полное имя класса
// Здесь можно настроить несколько интерфейсов DB или адресов пакетов, разделенных ","
// Напоминание: в структуре JSON массива последний элемент не может иметь "," в конце
]
},
/*
Настройка другой области источника данных
*/
{
"config" : "mySQLDriver", // Указывает, что mySQLDriver отвечает за предоставление источника данных
"dataSourceName": "shtest_db", // Имя источника данных
"basePackages": [ // Область действия этого источника данных
"org.fastquery.example.DataAcquireDbService"
// Здесь можно настроить несколько DB интерфейсов, разделенных ","
]
},
{
"config": "c3p0", // Указывает, что c3p0 отвечает за предоставление источника данных
"basePackages": [
"org.fastquery.dao2.UserInfoDB"
]
}
]
}
Примечание: В fastquery.json при настройке области действия, «dataSourceName» не является обязательным, если он не указан, то должен быть правильно настроен. Если «dataSourceName» не указано, то при вызове интерфейса необходимо указать имя источника данных. В следующем разделе о настройке источника данных будет рассказано. «basePackages», если конфигурация выполнена с указанием адреса пакета, соответствующий источник данных будет действовать для всех классов в этом пакете и всех подпакетов.
Инициализация источника данных начинается с fastquery.json, в соответствии со значением, прочитанным из «dataSourceName», выбирается соответствующая конфигурация, и завершается создание источника данных. Например, создание источника данных с именем «rex-db»:
{
"config": "c3p0",
"dataSourceName": "rex-db"
}
Здесь «basePackages» не требуется, этот источник данных можно использовать как сервис, предоставляемый без явного указания источника данных Repository.
Когда вы видите пример, не делайте поспешных выводов, часто результаты могут быть неожиданными.
public class Student
{
private String no;
private String name;
private String sex;
private Integer age;
private String dept;
// getter / setter 省略...
}
Атрибуты сущности должны быть упакованы, иначе они будут проигнорированы. Пометьте атрибут как @Transient
, чтобы указать, что атрибут не участвует в отображении.
public interface StudentDBService extends org.fastquery.core.Repository {
@Query("select no, name, sex from student")
JSONArray findAll();
@Query("select no,name,sex,age,dept from student")
Student[] find();
}
public class StudentDBServiceTest {
// Получение реализации класса
private static StudentDBService studentDBService = FQuery.getRepository(StudentDBService.class);
@Test
public void test() {
// Вызов метода findAll
JSONArray jsonArray = studentDBService.findAll();
// вызов метода find
Student[] students = studentDBService.find();
}
}
Примечание: Не нужно реализовывать интерфейс StudentDBService. Через FQuery.getRepository
получите экземпляр, соответствующий интерфейсу StudentDBService, хотя каждый раз, когда экземпляр извлекается, производительность незначительна, но, поскольку это интерфейс, который часто вызывается, рекомендуется присвоить полученное значение статическому члену класса. Экземпляр, полученный через FQuery.getRepository
, является уникальным и неизменным.
Интерфейс не реализует свои public abstract
методы, поэтому соответствующий экземпляр объекта необходим, просто FastQuery реализует его внутренне для пользователя. Читатели могут спросить, когда генерируется автоматически сгенерированный экземпляр? Как поддерживается высокая эффективность динамического создания? Для этого автор проделал большую работу: все классы реализации DB создаются на этапе инициализации проекта, и максимально возможное количество статического анализа выполняется для методов интерфейса, чтобы ошибки, которые могут возникнуть во время выполнения, были максимально перенесены на этап инициализации, перед генерацией кода будет проверяться, является ли SQL-привязка допустимой и эффективной, соответствует ли возвращаемое значение метода обычным правилам, соответствуют ли параметры метода шаблону вызова, правильно ли используются разбиение по страницам... и другие подобные проблемы. Эти потенциальные проблемы будут раскрыты, и проект не запустится, подробные сообщения об ошибках будут выводиться на этапе разработки, и эти ошибки, которые должны возникать в производственной среде, должны быть устранены, чтобы продолжить разработку. Разработчик должен идти по правильному пути, или, другими словами, хорошая конструкция фреймворка направляет разработчика к написанию надежного программного обеспечения, или разработчик должен написать надежный код, в противном случае. Единственный выход — ссылаться на интерфейс. Это делает разработку проще, потому что разработчик имеет дело с высокоуровневой абстракцией и не должен вникать в детали. Интерфейс можно рассматривать как модель, которая может анализировать SQL и автоматически выполнять его. Параметры метода, связанные шаблоны и аннотации идентификации предназначены для одной цели: выполнения SQL и возврата результата.
Такой стиль разработки, ориентированный на интерфейсы, имеет много преимуществ: степень сцепления стремится к нулю, а открытость для расширения очевидна. Независимо от того, идёт ли речь о поддержке прикладного уровня или добавлении новых функций в структуру, всё становится намного проще. Скрытая реализация может уменьшить количество ошибок или даже устранить их. Решение проблемы всегда уступает устранению проблемы. Причина в том, что после решения проблемы нельзя доказать, что другая потенциальная проблема не возникнет в процессе исправления кода. Очевидно, устранение проблемы более эффективно. Прикладному уровню нужно только написать абстрактные методы и идентифицирующие аннотации. Откуда берутся ошибки?
Этот проект имеет большое преимущество, которое заключается в том, чтобы сделать невозможным создание ошибок разработчиками. По крайней мере, это очень сложно. Это одна из основных целей проекта.
Независимо от того, используете вы этот проект или нет, автор надеется, что читатели хотя бы бегло просмотрят этот документ. В нём есть много дизайнерских решений, которые отсутствуют во многих аналогичных фреймворках. Автор надеется, что читатель получит вдохновение от этого документа, будь то положительное или отрицательное. Даже небольшое вдохновение может принести вам пользу.
Происхождение @Query
После того как этот проект был открыт, некоторые разработчики, привыкшие к сложным кодам, выразили мнение, что использование @Query не так сильно, как @SQL, @Select, @Insert, @Update...
Полное название SQL — Structured Query Language. @Query в этой статье происходит от него. @Query служит только носителем для выполнения SQL. Что делать, решает сам SQL. Поэтому не следует односторонне думать, что @Query — это операция выбора.
Что касается аннотаций операций с базой данных, нет необходимости определять их в соответствии с четырьмя типами SQL (DDL, DML, DCL, TCL). Определение слишком сложное и ненужное. Если добавить аннотацию @Modifying к изменяющей операции, то все остальные будут «проверяющими». Разве это не более лаконично и практично? Например:
// sql中的?1 表示 соответствующий текущий метод первый параметр
// sql中的?2 表示 соответствующий текущему методу второй параметр
// ?N 表示 соответствующий текущему методу N-й параметр
// Возвращает массив
@Query("select no, name, sex, age, dept from student s where s.sex=:sex and s.age > ?1")
Student[] find(Integer age,@Param("sex")String sex);
// Возвращает JSON
@Query("select no, name, sex from student s where s.sex=:sex and s.age > ?2")
JSONArray find(@Param("sex")String sex,Integer age);
// Возвращает List Map
@Query("select no, name, sex from student s where s.sex=?1 and s.age > :age")
List<Map<String, Object>> findBy(String sex,@Param("age")Integer age);
// Возвращает List сущности
@Query("select id,name,age from `userinfo` as u where u.id>?1")
List<UserInfo> findSome(@Param("id")Integer id);
Не рекомендуется использовать вопросительные знаки (?) для ссылки на параметры, если параметров много, поскольку они связаны с порядком методов. Вместо этого можно использовать выражения с двоеточием (:), которые не связаны с порядком. ":name" означает ссылку на параметр с аннотацией @Param("name").
Если возвращается List<Map<String, String>> или Map<String, String>, значения полей в запросе будут заключены в строки.
Обратите внимание, что если данные не найдены, возвращаемое значение будет пустым объектом (empty object) для коллекций или JSON или массивом длины 0. Использование пустого объекта вместо null может избежать NullPointerException и предотвратить распространение null. Противники обычно рассматривают проблему с точки зрения производительности и считают, что создание нового пустого объекта увеличит нагрузку на систему. Однако автор «Effective Java» Джош Блох считает, что беспокоиться о производительности на этом уровне неразумно, если только анализ не показывает, что возврат пустого объекта является источником проблем с производительностью. Внимательные люди, возможно, уже заметили, что новые версии API JDK стараются избегать возврата null.
Например:
// Для этого метода, если данные не найдены, результатом будет Student[] длиной 0
@Query("sql statements")
Student[] find(Integer age,String sex);
// Для этого метода, если данные не найдены, результат будет пустой Map (не null)
@Query("sql statements")
Map<String,Object> find(Integer id);
// Для этого метода, если данные не найдены, результатом будет List<Map> (не null), длина которого равна 0
@Query("sql statements")
List<Map<String, Object>> find(String sex);
Примечание: при поиске одного поля, также поддерживаются следующие типы возврата:
За исключением операций изменения и подсчёта, поиск одного поля не может возвращать базовые типы, потому что базовые типы не могут принимать значения null, а поля таблицы SQL могут быть нулевыми. Если возвращаемый тип является типом оболочки базового типа, и возвращается null, это означает, что данные не были найдены или найденные данные сами по себе являются нулевыми.
Например:
// Поиск одного поля, если данные не найдены, возвращает пустой List<String> (не null)
@Query("select name from Student limit 3")
List<String> findNames();
Как сопоставить атрибуты класса с полями таблицы, когда они не совпадают?
Чтобы проиллюстрировать эту проблему, сначала создайте объект сущности:
public class UserInformation {
private Integer uid;
private String myname;
private Integer myage;
// getters / setters
// ... ...
}
Поля таблицы базы данных — id, name и age. Используя псевдонимы SQL, можно решить проблему сопоставления атрибутов класса и полей таблицы. Например:
// Сопоставление результатов запроса с UserInformation
@Query("select id as uid,name as myname,age as myage from UserInfo u where u.id = ?1")
UserInformation findUserInfoById(Integer id);
Динамический запрос условий
Здесь мы видим, что SQL можно писать только в аннотациях (аннотациях). FastQuery также предоставляет два других решения: ① Используйте @QueryByNamed (именованный запрос), чтобы поместить SQL в файл шаблона и разрешить сложные логические суждения в файле шаблона, что очень гибко. ② Используйте QueryBuilder для построения SQL. Ниже приводится подробное описание. ``` ?2") // 参数 ?2, если полученное значение равно null, это условие не участвует в операции // Условие включается в операцию, даже если значение равно null, благодаря ignoreNull=false @Condition(value = "and age > ?3",ignoreNull=false) // Если полученное значение для ?3 равно null, то это условие сохраняется @Condition("and name not like ?4") @Condition("or age between ?5 and ?6") Student[] findAllStudent(... args ...);
**Примечание**:
- Если параметр имеет тип String, и значение равно null или "", то по умолчанию условие удаляется.
- `ignoreNull=false`: значение параметра равно null, но условие всё равно участвует в операции.
- `ignoreEmpty=false`: значение параметра равно "", но условие сохраняется.
`@Condition(value="name = ?1",ignoreNull=false)` означает, что если полученное значение для `?1` равно `null`, то условие также участвует в операции, и в итоге будет переведено как `name is null`. В SQL `null` нельзя использовать с операторами сравнения (`=`, `<`, `<>`), но можно с операторами `is null` и `is not null`, `<=>`, поэтому `name = null` интерпретируется как `name is null`.
`@Condition(value="name != ?1",ignoreNull=false)`: если полученное значение для `?1` равно `null`, в итоге будет интерпретировано как `name is not null`.
При использовании `In` запроса в качестве условия, игнорируйте проверку на null. Например, `@Condition("or dept in(?4,?5,?6)"`, где один из параметров равен `null`, условие будет удалено, что не совсем логично.
### Управление условиями с помощью JAVA-скрипта
В `@Condition` есть атрибут `ignoreScript`, который позволяет привязать JAVA-скрипт (не JS). Скрипт выполняется, и результат используется для определения, следует ли сохранить условие. Если результат равен `true`, то условие удаляется, иначе — сохраняется. По умолчанию скрипт возвращает `false`, что означает сохранение условия. Обратите внимание: результат выполнения скрипта должен быть булевым типом, иначе проект не запустится.
Пример:
```java
@Query("select id,name,age from `userinfo` #{#where}")
@Condition("age > :age")
@Condition(value="and name like :name",ignoreScript=":age > 18 && :name!=null && :name.contains(\"Rex\")")
Page<UserInfo> find(@Param("age")int age,@Param("name")String name,Pageable pageable);
Здесь :age
ссылается на фактический аргумент метода @Param("age") int age
. :name
— на фактический аргумент @Param("name") String name
. Смысл этого скрипта очевиден. Однако скрипт не может автоматически распаковывать значения, необходимо вызывать методы распаковки. Здесь, если age
имеет тип Integer
, чтобы скрипт работал правильно, нужно сделать так: ":age.intValue() > 18 && :name!=null && :name.contains(\"Rex\")"
, обратите внимание на :age.intValue()
. То же самое касается других типов-обёрток: Short
, Long
, Byte
, Boolean
, Character
, Float
, Double
и т. д.
Строка, содержащая код программы, называется JAVA-скриптом. Скрипт интерпретируется во время инициализации и превращается в байт-код, который может выполняться в JVM. В скрипте можно получить все параметры текущего метода через :expression
(выражение с двоеточием). Параметры могут быть сложными объектами, и :expression
можно рассматривать как ссылку на объект. Хотя скрипты могут быть длинными и содержать сложные логики, рекомендуется избегать этого, так как они трудночитаемы и неудобны для поддержки и изменения. Даже для сложных программ лучше разбивать их на небольшие и простые функции, а затем объединять их с использованием хорошего дизайна. FastQuery всегда придерживается простоты, строгости и ясности в программировании.
Условие сохраняется или удаляется в зависимости от результата выполнения if
условия. Если результат true
, условие сохраняется, иначе используется else
. Если else
отсутствует или пустое, условие удаляется. Пример:
@Query("select id,name,age from `userinfo` #{#where}")
@Condition(value="age > :age",if$=":age < 18", else$="name = :name")
Page<UserInfo> findPage(@Param("age")int age,@Param("name")String name,Pageable pageable);
Если фактическое значение age
меньше 18, условие сохраняется, в противном случае оно становится name = :name
. Конечно, else
не является обязательным, и если if
возвращает false, условие просто удаляется.
Иногда для принятия решения о сохранении условия требуется выполнить несколько вычислений с разными параметрами. Это сложно выразить с помощью JAVA-скрипта, или разработчик не хочет его использовать. Тогда можно использовать атрибут ignore
в @Condition
, указав класс, называемый Judge
. Этот класс решает, удалять ли условие. Пример: удалить условие and name like :name
, если возраст больше 18 и имя не пустое и содержит «Rex».
Создайте класс LikeNameJudge
, наследующий от org.fastquery.where.Judge
. IDE предложит реализовать метод ignore
, иначе появится ошибка. Это помогает избежать ошибок. Если метод ignore
возвращает true
, условие удаляется; если он возвращает false
, условие сохраняется.
public class LikeNameJudge extends Judge {
@Override
public boolean ignore() {
// Получить параметр метода с именем "age"
int age = this.getParameter("age", int.class);
// Получить параметр метода с именем "name"
String name = this.getParameter("name", String.class);
return age > 18 && name!=null && name.contains("Rex");
}
}
В области видимости LikeNameJudge
можно получить все фактические аргументы метода. Эти аргументы определяют, будет ли условие сохранено.
Укажите LikeNameJudge
:
@Query("select id,name,age from `userinfo` #{#where}")
@Condition("age > :age")
@Condition(value="and name like :name",ignore=LikeNameJudge.class)
Page<UserInfo> find(@Param("age")int age,@Param("name")String name,Pageable pageable);
По умолчанию атрибут ignore
указывает на DefaultJudge
, который ничего не делает.
Если @Condition
использует значение ${выражение}
, $выражение
, независимо от переданных параметров, условие не удаляется, потому что $
выражение (или EL выражение) используется только как шаблон, и null заменяется на «» (пустая строка). Пример:
@Query("select * from `userinfo` #{#where}")
@Condition("age between $age1 and ${age2}")
List<Map<String, Object>> between(@Param("age1") Integer age1,@Param("age2") Integer age2);
В этом примере @Condition
использует $
выражение ($age1
, ${age2}
), которое используется только в качестве шаблона. Если age1
равен null, даже при установленном ignoreNull=true
условие не изменится. Короче говоря, $
выражение не влияет на существование условия.
Для одного @Condition
с несколькими SQL
параметрами, такими как @Condition("or age between ?5 and ?6")
или @Condition("or age between :age1 and :age2")
Любой из параметров ?5, ?6, :age1 или :age2, равный null, приведёт к удалению этого условия из строки.
Тип сопоставления
Это не строгое сопоставление типов, достаточно, чтобы размер хранилища Java-типа был больше или равен размеру хранилища SQL-типа. Иными словами, если Java-тип может вместить значение, то всё в порядке, иначе — нет.
Java | SQL |
---|---|
Boolean | BIT(1), TINYINT(1), CHAR(1) |
Byte | TINYINT |
Short | SMALLINT |
Integer | INT |
Long | BIGINT |
Float | FLOAT |
Double | DOUBLE |
Character | CHAR(1) |
Enum | ENUM |
EnumSet | SET |
String | Все типы |
count
Подсчитывает количество строк в запросе.
@Query("select count(no) from student")
long count();
exists
Определяет, существует ли объект.
@Query("select no from student s where s.no=?1")
boolean exists(String no);
Операции изменения
// Возвращает количество затронутых строк
@Query("update student s set s.age=?3,s.name=?2 where s.no=?1")
@Modifying
int update(String no,String name,int age);
// Если операция выполнена успешно, возвращает true, в противном случае — false
@Modifying
@Query("delete from `userinfo` where id=?1")
boolean deleteUserinfoById(int id);
// В формате bean-компонента возвращает текущие сохранённые данные
@Query("insert into student (no, name, sex, age, dept) values (?1, ?2, ?3, ?4, ?5)")
@Modifying(table="student",id="no")
Student addStudent(@Id String no,String name,String sex,int age,String dept);
// В формате JSON возвращает текущие сохраненные данные
@Modifying(id="id",table="userinfo")
@Query("insert into #{#table} (name,age) values (?1, ?2)")
JSONObject saveUserInfo2(String name,Integer age);
// Возвращает информацию о первичном ключе сохраненных данных
@Modifying(id="id",table="userinfo")
@Query("insert into #{#table} (name,age) values (?1, ?2)")
Primarykey saveUserInfo(String name,Integer age);
Добавляет запись и возвращает объект, можно использовать @Modifying
для явного указания полей, которые нужно запросить. Например:
// В формате Map возвращает текущие сохраненные данные
@Modifying(id="id",table="userinfo",selectFields="name,age")
@Query("insert into #{#table} (name,age) values (?1, ?2)")
Map<String, Object> addUserInfo(String name,Integer age);
Здесь selectFields
по умолчанию равен *
, поля разделяются запятой.
Примечание:
Annotation
Все аннотации в FastQuery описаны ниже:
Annotation | Описание |
---|---|
@Id |
Используется для обозначения первичного ключа таблицы |
@Table |
Указывает имя таблицы |
@Modifying |
Помечает операцию изменения |
@Param |
Обозначает имя параметра, которое удобно для получения во время выполнения |
@Query |
Определяет запрос |
@QueryByNamed |
Определяет именованный запрос (запрос находится в файле конфигурации) |
@Source |
Описывает параметры, используемые для адаптации к источнику данных |
@Transactional |
Транзакция |
@Transient |
Отмечает временное свойство объекта (например, при сохранении объекта это свойство не сохраняется в базе данных) |
@NotCount |
Не учитывает общее количество строк при разбиении на страницы |
@PageIndex |
Индекс страницы |
@PageSize |
Количество строк на странице |
@Condition |
Условный блок |
@Set |
Блок установки поля |
Методы QueryRepository
Все классы, наследующие интерфейс QueryRepository
, могут использовать его методы без необходимости реализации класса. Некоторые встроенные методы включают:
Метод | Описание |
---|---|
<E> E find(Class<E> entityClass,long id) |
Поиск сущности по первичному ключу |
<E> int insert(E entity) |
Вставка сущности (если значение первичного ключа равно null, этот столбец не участвует в операции), возвращает количество затронутых строк |
<B> int save(boolean ignoreRepeat,Collection<B> entities) |
Сохранение коллекции сущностей, игнорирование существующих уникальных записей (может быть несколько полей, составляющих уникальный ключ) |
int saveArray(boolean ignoreRepeat,Object...entities) |
Сохраняет массив сущностей, игнорируя существующие уникальные записи (может быть несколько полей, составляющих уникальный ключ) |
BigInteger saveToId(Object entity) |
После сохранения сущности возвращает значение первичного ключа.Примечание: тип первичного ключа должен быть числовым и автоматически увеличивающимся, не поддерживает составные первичные ключи |
<E> E save(E entity) |
Сохраняет сущность и возвращает её |
<E> int executeUpdate(E entity) |
Обновляет сущность, возвращает количество затронутых строк.Примечание: свойства сущности, равные null, не участвуют в обновлении |
<E> E update(E entity) |
Обновляет сущность и возвращает обновлённую сущность |
<E> int executeSaveOrUpdate(E entity) |
Если сущность не существует, сохраняет её, в противном случае обновляет (при условии, что сущность содержит первичный ключ, а значение первичного ключа равно null, она сохраняется напрямую), возвращает количество затронутых строк |
<E> E saveOrUpdate(E entity) |
Если сущность не существует, сохраняет её, в противном случае обновляет (при условии, что сущность содержит первичный ключ, а значение первичного ключа равно null, она сохраняется напрямую), возвращает обновленную или сохраненную сущность |
int update(Object entity,String where) |
При обновлении сущности можно указать собственное условие (не всегда обновление происходит по первичному ключу), если передать where как null или "", обновление будет происходить по первичному ключу, возвращает количество затронутых строк |
<E> int update(Collection<E> entities) |
Обновление коллекции сущностей, свойства, равные null, не будут участвовать в обновлении, каждая сущность должна содержать первичный ключ |
int delete(String tableName,String primaryKeyName,long id) |
Удаление сущности по первичному ключу, возврат количества затронутых строк |
int[] executeBatch(String sqlName) |
Выполнение пакетных операций с использованием указанного имени файла SQL или абсолютного пути, возврат массива int[], каждый элемент соответствует количеству затронутых строк после выполнения одного SQL-запроса |
<E> long count(E entity)
— подсчитывает общее количество записей в соответствии с указанным условием. Если атрибут сущности равен null, то этот атрибут не участвует в операции, иначе участвует в логическом «и».
Сначала создадим сущность:
public class UserInfo {
@Id
private Integer id;
private String name;
private Integer age;
// getter /setter 省略...
}
Используем встроенный метод QueryRepository и наследуем его:
public interface StudentDBService extends QueryRepository {
... ...
}
Примечание: наследование Repository подходит для сценариев, где встроенные методы не используются, что делает его более лёгким.
Сохраняем сущность, обновляем сущность, сохраняем или обновляем сущность:
UserInfo u1 = new UserInfo(36,"Dick", 23);
// Сохраняем сущность
studentDBService.save(u1)
Integer id = 36;
String name = "Dick";
Integer age = null;
UserInfo u2 = new UserInfo(id,name,age);
// age — это null значение, age не будет участвовать в изменении.
studentDBService.update(u2); // Обновление запроса: update UserInfo set name = ? where id = ?
// Сохраняем или обновляем сущность
studentDBService.saveOrUpdate(u1);
При использовании метода update можно также указать пользовательские условия:
Integer id = 1;
String name = "可馨";
Integer age = 3;
UserInfo entity = new UserInfo(id,name,age);
// Будет проанализировано:update `UserInfo` set `id`=?, `age`=? where name = ?
int effect = studentDBService.update(entity,"name = :name");
// Утверждение: количество затронутых строк больше 0.
assertThat(effect, greaterThan(0));
// Не хотим, чтобы атрибут id участвовал в изменении, поэтому устанавливаем его значение равным null
entity.setId(null);
// Будет проанализировано:update `UserInfo` set `age`=? where name = ?
effect = studentDBService.update(entity,"name = :name");
assertThat(effect, greaterThan(0));
Пакетное обновление (update), если все записи обновляются одинаково, не требует дополнительных комментариев. Здесь основное внимание уделяется пакетному обновлению разных полей и с разным содержимым.
Пример:
Требуется:
Реализация кода:
// Шаг 1: Подготавливаем сущности для изменения
List<UserInfo> userInfos = new ArrayList<>();
userInfos.add(new UserInfo(77,"茝若", 18));
userInfos.add(new UserInfo(88,"芸兮", null));
userInfos.add(new UserInfo(99,null, 16));
// Шаг 2: Пакетное обновление
int effect = userInfoDBService.update(userInfos);
assertThat(effect, is(3));
В итоге будет проанализирована одна команда SQL:
update UserInfo set
name = case id
when 77 then '茝若'
when 88 then '芸兮'
else name end
,
age = case id
when 77 then '18'
when 99 then '16'
else age end
where id in (77, 88, 99)
Часто требуется изменять только отдельные поля таблицы: в A необходимо изменить поле x таблицы, в B — поле y таблицы, а в C — одновременно поля x и y. Цель разработки @Set — удовлетворить подобные требования. В зависимости от переданных параметров динамически добавляются необходимые поля, чтобы одна команда SQL удовлетворяла нескольким требованиям.
@Modifying
@Query("update `Course` #{#sets} where no = ?5")
@Set("`name` = ?1") // ?1 Если null или "", то эта строка set удаляется
@Set("`credit` = ?2")
@Set("`semester` = ?3")
@Set("`period` = ?4")
int updateCourse(String name,Integer credit, Integer semester, Integer period, String no);
#{#sets}
используется для ссылки на параметры настройки. @Set(value="name = ?1" , ignoreNull=true , ignoreEmpty=true)
— необязательные параметры конфигурации, как следует из названия.
Все @Set
в методе могут быть полностью удалены, что приведёт к созданию некорректного SQL update Course set where no = ?5
. Чтобы избежать этой ошибки, есть два способа: 1). добавить строку без параметров @set("name = name")
, которая никогда не будет удалена и не повлияет на исходные данные; 2). проверить параметры перед вызовом метода, чтобы исключить возможность удаления всех @set
из-за параметров.
Один @Set
может применяться к ситуациям, когда появляется несколько параметров SQL, например @Set("name = ?1","credit = ?2")
или @Set("name = :name","credit = :credit")
, где любой из параметров ?1
, ?2
, :name
, :credit
, равный null
, приведёт к удалению этой строки настроек.
Атрибут ignoreScript
в @Set
позволяет привязать JAVA-скрипт (не JS), который определяет, следует ли сохранить параметр настройки на основе результата выполнения скрипта. Если результат выполнения скрипта — true
, то строка настроек удаляется, в противном случае — сохраняется. По умолчанию скрипт возвращает false
, что означает сохранение параметра настройки. Обратите внимание: результат выполнения скрипта должен быть булевым значением, иначе проект не запустится.
Например:
@Modifying
@Query("update `Course` #{#sets} where no = ?3")
@Set(value="`name` = :name",
ignoreScript=":name!=null && :name.startsWith(\"计算\") && :credit!=null && :credit.intValue() > 2")
@Set("`credit` = :credit")
int updateCourse(@Param("name") String name,@Param("credit") Integer credit,String no);
Здесь :credit
ссылается на фактический параметр credit
, а :name
— на фактический параметр name
. Этот скрипт выражает очевидное условие. Однако следует отметить, что скрипт не может автоматически выполнять распаковку(unboxing), необходимо использовать соответствующий метод, обратите внимание на :credit.intValue()>2
. Если записать :credit > 2
, компиляция не удастся. То же самое относится и к другим типам упаковки: Short
, Long
, Byte
, Boolean
, Character
, Float
, Double
и т. д.
Скрипт компилируется на этапе инициализации проекта, поэтому проблем с производительностью нет. Рекомендуется избегать длинных скриптов, так как это ухудшает читаемость.
Будет ли сохранён параметр настройки SQL, определяется с помощью if
...else
... . Выражение if
связывается с оператором =
. Если if
истинно, то текущий параметр настройки сохраняется, в противном случае используется значение else
. Конечно, else
не является обязательным в синтаксисе, если if
ложно, то текущая строка @Set
удаляется напрямую.
Например:
@Modifying
@Query("update `User` #{#sets} where id = ?3")
@Set(value="`name` = :name",if$="!:name.contains(\"root\")",else$="`name` = name") ```
updateUser(@Param("name") String name, int id);
где, если значение name
не содержит "root", то сохраняется "name = :name"
этот параметр, иначе устанавливается параметр name = name
, что означает сохранение значения name
.
Решение о том, будет ли элемент Set участвовать в операции, может быть принято на основе нескольких параметров. @Set
позволяет связать пользовательский класс Judge
для выполнения вычислений. Например, если префикс значения name
равен «вычисление» и значение credit
больше 2, то параметр name = :name
удаляется.
Класс NameJudge
:
public class NameJudge extends Judge {
@Override
public boolean ignore() {
// Получаем значение параметра с именем "name"
String name = this.getParameter("name", String.class);
// Получаем значение параметра с именем "credit"
Integer credit = this.getParameter("credit", Integer.class);
return name.startsWith("вычисление") && credit != null && credit > 2;
}
}
Связывание параметра NameJude
:
@Modifying
@Query("update `Course` #{#sets} where no = ?3")
@Set(value="`name` = :name",ignore=NameJudge.class)
@Set("`credit` = :credit")
int updateCourse(@Param("name") String name, @Param("credit") Integer credit, String no);
Есть три способа сделать параметр name
недействительным:
name
равно null
;name
— пустая строка (`"");ignore
класса NameJudge
возвращает true
.Помимо использования @Set
, есть несколько других способов динамически добавлять или удалять параметры разных полей:
а. Вызов встроенного метода int executeUpdate(E entity)
. Если поле сущности равно null
, то это поле не будет участвовать в операции set;
б. Использование SQL-шаблонов для логической проверки;
в. Применение QueryBuilder
;
г. Использование $ выражения
. Разработчику будет сложно выбрать подходящий способ.
@Transactional
// Три операции изменения объединяются в одну транзакцию.
@Transactional
@Modifying
@Query("update `userinfo` set `name`=?1 where id=?3")
@Query("update `userinfo` set `age`=?2 where id=?3")
// Устанавливает идентификатор равным 1, где текущий идентификатор равен 1 (ошибка).
// Таким образом, все предыдущие операции становятся недействительными.
@Query("update `userinfo` set `id`=1 where `id`=?3")
int updateBatch(String name, Integer age, Integer id);
// Примечание:
// 1). Возвращаемое значение типа int представляет собой общее количество строк, затронутых успешными операциями изменения после фиксации транзакции.
// 2). Возвращаемое значение int[] представляет собой набор количества строк, затронутых каждой минимальной единицей изменения, после успешной фиксации транзакции.
// Пример: если транзакция T содержит три операции изменения U1, U2 и U3, а N1, N2 и N3 представляют количество затронутых строк для U1, U2 и U3 соответственно, то возвращаемое значение будет new int[]{N1, N2, N3}.
В QueryRepository
есть встроенная функция транзакции tx
.
int effect = userInfoDBService.tx(() -> {
// Поместите операции изменения, которые должны быть включены в транзакцию, сюда.
// update1
// to do...
// update2
// Вернуть количество затронутых строк.
});
В лямбда-выражении ()->{}
все операции являются атомарными: либо все успешно выполняются, либо все терпят неудачу и откатываются. Лямбда-выражения закрываются по значениям, но не по переменным. Из-за этой особенности нельзя изменять значение внешней переменной в {}
, но можно присвоить значение внешнему объекту.
... ...
Map<String, Object> map = new HashMap<>();
int sum = 0;
tx(() -> {
sum = sum + 1; // Компиляция завершится ошибкой, поскольку нельзя изменить значение sum (Illegal, close over values).
map.put(K, V); // Это разрешено (Legal, open over variables).
});
Таким образом, данные из {}
можно извлечь, назначив их внешнему объекту. if(?2 > 10,'大于10','不大于10') as msg -- 名称为"number"的参数,其值可以影响where条件 select t.A from (select 11 as A,22 as B,33 as C) as T where if(:number > 10,t.B>10,t.C>100) -- 名称为"number"的参数,其值可以 повлиять на результат запроса select if(?number > 10,'больше 10','меньше или равно 10') as msg
允许多个方法绑定同一个模板id. 在模板中使用`${_method}`可以 ссылаться на текущий метод `org.fastquery.core.MethodInfo` объекта, который является отражением `java.lang.reflect.Method` кэша.
Пример: в зависимости от имени текущего метода получить разные SQL-запросы.
```java
public interface QueryByNamedDBExtend extends QueryRepository {
@QueryByNamed(render = false)
JSONArray findUAll();
// 两个 метода указывают на одно и то же значение template id
@QueryByNamed("findSome")
JSONArray findLittle();
@QueryByNamed("findSome")
JSONArray findSome();
}
Содержимое файла org.fastquery.dao.QueryByNamedDBExtend.queries.xml шаблона:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<queries>
<!-- 定义全局 parts -->
<parts>
<part name="feids">id,name,age</part>
</parts>
<query id="findUAll">
<value>select #{#feids} from UserInfo limit 3</value>
</query>
<query id="findSome">
<![CDATA[
## 如果当前方法的名称 равно "findLittle"
#if( ${_method.getName()} == "findLittle" )
## получить 3 строки
select #{#feids} from UserInfo limit 3
#else
select `no`, `name` from Student limit 5
#end
]]>
</query>
</queries>
Здесь ${_method.getName()}
можно сократить до ${_method.name}
. В Velocity вызов объектов или методов не является основной темой статьи.
В статье описывается, что SQL можно привязать к @Query
или записать в XML. Также есть другой способ — построить QueryBuilder для создания запроса.
Пример использования:
@Query
Page<Map<String, Object>> pageByQueryBuilder(QueryBuilder queryBuilder,Pageable pageable);
Если не требуется получать общее количество страниц, можно добавить @NotCount
в интерфейс метода (не обязательно выполнять count-запрос для разбивки на страницы).
Не нужно реализовывать этот интерфейс, просто вызовите:
// Получение экземпляра Repository
UserInfoDBService db = FQuery.getRepository(UserInfoDBService.class);
String query = "select id,name,age from userinfo #{#where}";
String countQuery = "select count(name) from userinfo #{#where}";
ConditionList conditions = ConditionList.of("age > :age","and id < :id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("age", 18);
parameters.put("id", 50);
QueryBuilder queryBuilder = new QueryBuilder(query, countQuery, conditions, parameters);
Page<Map<String, Object>> page = db.pageByQueryBuilder(queryBuilder,new PageableImpl(1, 3));
List<Map<String, Object>> content = page.getContent();
content.forEach(map -> {
Integer age = (Integer) map.get("age");
Integer id = (Integer) map.get("id");
assertThat(age, greaterThan(18));
assertThat(id, lessThan(50));
});
List<String> executedSQLs = rule.getExecutedSQLs();
assertThat("Утверждение: выполнено 2 SQL-запроса",executedSQLs.size(), is(2));
assertThat(executedSQLs.get(0), equalTo("select id,name,age from userinfo where age > ? and id < ? limit 0,3"));
assertThat(executedSQLs.get(1), equalTo("select count(name) from userinfo where age > ? and id < ?"));
Используется вопросительный знак (?
) и двоеточие (:
) для выражений. Здесь ?1
обозначает первый параметр метода, а :age
соответствует параметру @Param("age")
. Использование этих выражений не вызывает проблем с внедрением.
Если таблица, которую вы хотите запросить, является переменной (или даже автоматически сгенерированной), поля также являются переменными, диапазон условий также является переменной, и весь SQL динамически генерируется, тогда вам придётся использовать QueryBuilder
, так как он имеет незаменимые функции. Однако если проблему можно решить с помощью @Query
шаблона, лучше использовать его, поскольку его дизайн позволяет избежать ошибок.
Поддерживаются только входные параметры (in
), но не выходные (out
). Если вы хотите вывести результаты обработки хранимой процедуры, используйте запрос select
внутри процедуры для вывода.
Например, чтобы вставить студента и вернуть общее количество записей студентов и текущий код, синтаксис хранимой процедуры:
delimiter $$
drop procedure if exists `xk`.`addStudent` $$
create procedure `xk`.`addStudent` (in no char(7), in name char(10), in sex char(2), in age tinyint(4),
in dept char(20))
begin
-- 定义变量
-- 总记录数
declare count_num int default 0;
-- 编码
declare pno varchar(7) default '';
insert into `student` (`no`, `name`, `sex`, `age`, `dept`) values(no, name, sex, age, dept);
select count(`no`) into count_num from student;
select `no` from student where `no`=no limit 0,1 into pno;
-- 输出结果
select count_num, pno;
end $$
delimiter ;
Вызов хранимой процедуры:
@Query("call addStudent(?1,:name,?3,?4,:dept)")
JSONObject callProcedure(String no,@Param("name")String name,String sex,int age,@Param("dept")String dept);
Примечание: часть текста запроса не удалось перевести из-за наличия специальных символов. Перевод текста на русский язык:
{
"size": 15, // ожидаемое количество строк на странице
"number": 1, // текущая страница, начиная с 1
"size": 15, // ожидаемое количество строк на странице (numberOfElements представляет фактическое количество найденных строк)
"numberOfElements": 6, // фактическое количество записей на текущей странице
"totalElements": 188, // общее количество строк
"totalPages": 13 // общее количество страниц
}
Примечание:
@NotCount
, это означает, что общее количество строк не подсчитывается при разбиении на страницы. В этом случае значение totalElements
в объекте разбивки на страницы равно -1L, а totalPages
равно -1. Остальные атрибуты действительны и верны.countField
и countQuery
становится бессмысленной.#{}limit
можно использовать не только в XML-файлах, но и в @Query
, без особых требований, рекомендуется не указывать #{}limit
.В настоящее время эта структура по умолчанию поддерживает разбиение на страницы для баз данных MySQL, Microsoft SQL Server, PostgreSQL, поэтому пространство для расширения очень велико и легко реализуется. Реализуйте класс org.fastquery.page.PageDialect
, чтобы переписать соответствующие методы для решения проблем в SQL. Для получения дополнительной информации см. org.fastquery.dialect.MySQLPageDialect
и org.fastquery.dialect.PostgreSQLPageDialect
.
String sqlName = "update.sql";
int[] effects = studentDBService.executeBatch(sqlName);
sqlName
указывает на базовый каталог SQL-файла. Обратите внимание: базовый каталог настраивается в fastquery.json
, а sqlName
также может быть абсолютным путём.int[]
, используемый для записи количества строк, затронутых после выполнения каждой строки в файле SQL. Если effects[x] = m
, это означает, что выполнение строки x повлияло на m
строк; если effects[y] = n
, это означает, что строка y повлияла на n
строк.#
или --
.Один источник данных может управлять несколькими базами данных, и SQL-файлы могут потребоваться для обслуживания разных баз данных в зависимости от параметров. Или SQL-файл содержит динамические части, которые необходимо различать в соответствии с переданными параметрами. В таком случае можно использовать executeBatch (String sqlName, String[] quotes)
, где второй параметр может использоваться в SQL-файле с помощью ссылки $[N]
, которая ссылается на N-й элемент массива.
drop table if exists $[0].demo_table;
Если вы хотите динамически создать новый источник данных во время работы проекта, используйте FQuery.createDataSource
.
// Имя источника данных
String dataSourceName = "xk1";
// Конфигурация пула соединений
Properties properties = new Properties();
properties.setProperty("driverClass", "com.mysql.cj.jdbc.Driver");
properties.setProperty("jdbcUrl", "jdbc:mysql://db.fastquery.org:3306/xk1");
properties.setProperty("user", "xk1");
properties.setProperty("password", "abc1");
// Создание источника данных
FQuery.createDataSource(dataSourceName, properties);
Используйте @Source
, чтобы указать, какой источник данных должен использоваться текущим методом Repository
. Эта функция очень полезна. В мультитенантной системе базы данных изолированы друг от друга, но имеют одинаковую структуру таблиц. Это делает использование этой функции очень удобным.
Обратите внимание: если @Source
указан перед параметром, этот параметр должен быть строкового типа.
@Query("select id,name,age from `userinfo` as u where u.age>?1")
Map<String, Object> findOne(Integer age, @Source String dataSourceName);
Если в файле fastquery.json
явно указан диапазон действия источника данных и интерфейс имеет @Source
, то используется источник данных, указанный в @Source
, а затем конфигурация файла.
По умолчанию уже поддерживаются пулы соединений, такие как c3p0
, druid
, hikari
, и разработчики могут легко расширить их. Пример: позвольте FastQuery
поддерживать пользовательские пулы соединений. Процесс реализации следующий:
Шаг 1: Создайте собственный класс, реализующий интерфейс org.fastquery.core.ConnectionPoolProvider
.
public class MyPoolProvider implements ConnectionPoolProvider {
@Override
public DataSource getDataSource(Resource resource, String dataSourceName) {
// Чтение конфигурации файла
InputStream inputStream = resource.getResourceAsStream(name);
.... ...
Properties props = new Properties();
props.setProperty(k, v);
... ...
// Создание экземпляра источника данных
return new MyDataSource(props);
}
}
Шаг 2: Зарегистрируйте его в pool-extend.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<pools name="providers">
<pool name="mypool" class="org.fastquery.<your.domain>.MyPoolProvider" />
</pools>
Шаг 3: Используйте пользовательский пул соединений mypool
. Настройте файл fastquery.json
.
{
"scope": [
{
"config": "mypool", // Использовать этот пул для предоставления источника данных
"dataSourceName": "hiworld",
"basePackages": [
"<your.domain>.XxxDBService"
]
}
]
}
Настройте область сканирования:
<context:component-scan base-package="org.fastquery.service" />
Или
@ComponentScan("org.fastquery.service")
Используйте db-интерфейс, чтобы внедрить экземпляр объекта.
@javax.annotation.Resource
private UserInfoDB userInfoDB;
Тестирование FastQuery позволяет легко решить следующие проблемы:
FastQueryTestRule
реализует TestRule
класса JUnit, который используется для расширения тестовых случаев. Вы можете получить выполненный SQL-оператор и соответствующие ему значения параметров в тестовом методе, чтобы выполнить утверждение. Добавьте аннотацию @Rollback
, чтобы контролировать, будет ли транзакция базы данных автоматически откатываться или фиксироваться после завершения выполнения тестового метода. По умолчанию транзакция автоматически откатывается после завершения теста, что позволяет достичь эффекта тестирования без влияния на базу данных (можно откатиться до состояния до изменения). Вот пример, обратите внимание на комментарии. Откат транзакции после выполнения метода
@Rollback(true) // После выполнения данного метода происходит автоматический откат транзакции. @Test public void update() { String no = "9512101"; String name = "清风习习"; int age = 17; int effect = studentDBService.update(no, name, age); // Утверждение: количество затронутых строк равно 1. assertThat(effect, is(1)); // Получаем SQL-операторы, связанные с правилами. List sqlValues = rule.getListSQLValue(); // Утверждение: после выполнения studentDBService.update создаётся одна SQL-инструкция. assertThat(sqlValues.size(), is(1)); SQLValue sqlValue = sqlValues.get(0); // Утверждение: созданная SQL-инструкции имеет вид «update student s set s.age=?,s.name=? where s.no=?» assertThat(sqlValue.getSql(), equalTo("update student s set s.age=?, s.name=? where s.no=?")); // Получение списка параметров SQL. List values = sqlValue.getValues(); // Утверждение: в данной SQL-инструкции три параметра. assertThat(values.size(), is(3)); // Утверждение: первый параметр имеет тип Integer и равен значению age. assertThat((values.get(0).getClass() == Integer.class) && (values.get(0).equals(age)), is(true)); // Утверждение: второй параметр имеет тип String и равен значению name. assertThat((values.get(1).getClass() == String.class) && (values.get(1).equals(name)), is(true)); // Утверждение: третий параметр имеет тип String и равен значению no. assertThat((values.get(2).getClass() == String.class) && (values.get(2).equals(no)), is(true)); }
Проверка количества выполненных SQL-инструкций
Количество выполненных SQL-запросов не всегда совпадает с количеством связанных SQL-инструкций. Например, при использовании пагинации не всегда выполняется запрос count. С помощью метода rule.getExecutedSQLs() можно получить список уже выполненных SQL-инструкций. Перед каждым выполнением метода DB все записи об истории удаляются, и подсчёт начинается заново.
assertThat(db.findPageWithWhere(id, cityAbb, 6, pageSize).isHasContent(), is(true)); // Проверка наличия содержимого после выполнения запроса. // Получение выполненных SQL-инструкций после выполнения запроса. List executedSQLs = rule.getExecutedSQLs(); // Утверждение о том, что было выполнено две SQL-инструкции. assertThat(executedSQLs.size(), is(2)); // Утверждение о виде второй SQL-инструкции: «select count(id) from City where id > ? and cityAbb like ?». assertThat(executedSQLs.get(1), equalTo("select count(id) from City where id > ? and cityAbb like ?"));
assertThat(db.findPageWithWhere(id, cityAbb, 7, pageSize).isHasContent(), is(false)); // Проверка отсутствия содержимого после выполнения запроса. executedSQLs = rule.getExecutedSQLs(); // Получение выполненных SQL-инструкций после выполнения запроса. assertThat(executedSQLs.size(), is(1)); // Утверждение об одной выполненной SQL-инструкции. assertThat(executedSQLs.get(0), not(containsString("count"))); // Утверждение о типе первой SQL-инструкции без использования count.
Обеспечение стабильности FastQuery
FastQuery разрабатывается уже долгое время, и каждый раз при выпуске новой версии необходимо гарантировать, что существующие функции не будут нарушены. Это достигается благодаря тщательному тестированию каждой функции с использованием утверждений. Перед выпуском проверяется, проходят ли все утверждения успешно. Также стоит отметить продуманный дизайн системы.
JUnit является одним из немногих полезных фреймворков среди множества Java-фреймворков, и FastQuery активно его использует.
fastquery.json предоставляет дополнительные опции конфигурации:
Свойство | Тип | Значение по умолчанию | Назначение | Пример |
---|---|---|---|---|
basedir | string | Нет | Базовый каталог, обратите внимание на добавление "/" в конце. Этот каталог используется для размещения файлов SQL, которые необходимо выполнить. При выполнении файла SQL достаточно указать его имя. | "/tmp/sql/" |
debug | boolean | false | В режиме отладки можно динамически загружать SQL-запросы из XML-файла в каталоге classpath. По умолчанию режим отладки выключен. Не рекомендуется включать этот режим в рабочей среде. | false |
queries | array | [] | Указывает каталоги в файлах classpath, где могут находиться файлы шаблонов SQL (*.queries.xml). По умолчанию разрешено размещение в корне каталога classpath. Обратите внимание, что расположение конфигурационных файлов может быть не основано на каталоге classpath и может быть указано отдельно через "fastquery.config.dir". | ["queries/", "tpl/"] |
slowQueryTime | int | 0 | Устанавливает максимальное время выполнения медленных запросов (в миллисекундах). Если метод QueryRepository выполняется дольше этого времени, будет выдано предупреждение о методах, выполнение которых заняло много времени, чтобы оптимизировать их. | 50 |
Исходный код доступен на GitHub и Gitee. Рекомендуется использовать IDE IntelliJ IDEA и сборку Maven.
Для обсуждения FastQuery предлагается присоединиться к группе в WeChat. Автор открыт для предложений и готов обсуждать проект.
Проблемы и предложения по улучшению проекта можно оставить на странице с проблемами. FastQuery стремится к открытости и свободе обмена идеями, поэтому после каждого обновления код и документация становятся доступными для всех. Несмотря на ограниченные знания автора, ошибки и недочёты неизбежны, поэтому любые проблемы или предложения приветствуются.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )