Weed3 для Java: новый микро-ORM фреймворк
Weed3 — это микро-фреймворк ORM (Object-Relational Mapping), который поддерживает Java SQL, XML SQL, аннотации SQL; шаблонный SQL; транзакции; кэширование; прослушивание и т. д.
Он представляет собой многофункциональный и компактный ORM-фреймворк: его размер составляет 0,2 Мб, и он не зависит от других библиотек. Его внешний интерфейс также невелик, и основная работа выполняется через четыре интерфейса в DbContext.
Компонент | Описание |
---|---|
org.noear:weed3 | Основной фреймворк (без зависимостей) |
Опциональный компонент | Описание |
---|---|
org.noear:weed3-maven-plugin | Maven плагин для генерации Xml SQL маппера |
org.noear:weed3-solon-plugin | Solon плагин, поддерживающий @Db аннотацию и прямое внедрение Mapper |
org.noear:weed3.cache.memcached | Расширенная служба кэширования на основе Memcached |
org.noear:weed3.cache.redis | Расширенная служба кэширования на основе Redis |
org.noear:weed3.cache.ehcache | Расширенная служба кэширования на основе ehcache |
org.noear:weed3.cache.j2cache | Расширенная служба кэширования на основе j2cache |
<!-- Фреймворк пакет -->
<dependency>
<groupId>org.noear</groupId>
<artifactId>weed3</artifactId>
<version>3.4.33</version>
</dependency>
<!-- Maven плагин, используемый для создания Xml SQL мапперов -->
<plugin>
<groupId>org.noear</groupId>
<artifactId>weed3-maven-plugin</artifactId>
<version>3.4.33</version>
</plugin>
Если используется Spring, можно получить конфигурацию через аннотации.
В случае использования Solon, конфигурация может быть получена через аннотации или интерфейсы.
// Пример использования свойств для конфигурации:
Properties properties = Solon.cfg().getProp("demo.db"); // Это интерфейс Solon
DbContext db = new DbContext(properties);
// Пример использования proxool пула потоков для конфигурации (кажется, сейчас не используется):
DbContext db = new DbContext("user","proxool.xxx_db");
// Пример использования DataSource для конфигурации (обычно используется при использовании пулов соединений, рекомендуется Hikari):
DataSource dataSource = new HikariDataSource(...);
DbContext db = new DbContext("user", dataSource);
// Также можно использовать URL, имя пользователя и пароль (в этом случае настройка не требуется):
DbContext db = new DbContext("user","jdbc:mysql://x.x.x:3306/user","root","1234");
/* Я обычно использую конфигурацию служб, поэтому напрямую получаю объект контекста базы данных из конфигурации. */
// Использование конфигурационной службы для прямого получения объекта контекста базы данных:
DbContext db = WaterClient.Config.get("demo.db").getDb();
Четыре основных интерфейса также представляют собой четыре различных подхода к использованию DbContext в разных сценариях.
Основные интерфейсы: db.mapper() и db.table(). Они представляют два совершенно разных стиля и вкуса.
Дополнительные интерфейсы: db.call() и db.sql(). Они предназначены для особых сценариев использования.
Стиль mapper сейчас очень популярен. Большинство людей используют его.
Этот интерфейс предоставляет базовый режим Mapper, режим аннотаций @Sql и режим конфигурации Xml sql. В последнем случае внутренняя обработка Xml sql будет предварительно скомпилирована в класс Java во время запуска; производительность должна быть хорошей (похоже на предварительную компиляцию JSP).
Кажется, что все, у кого нет BaseMapper, немного смущаются называть себя ORM фреймворком.
Этот интерфейс действительно предоставляет множество методов, полностью избавляя от необходимости писать простой CRUD.
// Непосредственное использование BaseMapper
BaseMapper<User> userDao= db.mapperBase(User.class);
// Вставка:
userDao.insert(user,false); // false: исключить нулевые значения
// Удаление:
userDao.deleteById(12);
// Обновление: через ID
userDao.updateById(user,false); // false: исключить нулевые значения
// через условие
userDao.update(user,false,m->m.whereEq(User::getType,12).andEq(User::getSex,1));
// Поиск: через ID
User user = userDao.selectById(12);
// поиск: через условие
User user = userDao.selectItem(m -> m.whereEq(User::getId,12));
@Namespace("demo.dso.db")
public interface UserDao { // Этот интерфейс может расширять BaseMapper<T>
@sql("select * from `user` where id=@{id}") // стиль переменных
User getUserById(int id);
@sql("select * from `user` where id=?") // стиль заполнителя
User getUserById2(int id);
long addUser(User m); // Нет аннотации, необходимо написать конфигурацию xml sql
}
UserDao userDao = db.mapper(UserDao.class);
User user = userDao.getUserById(12);
userDao.addUser(user);
Преимущество этого интерфейса заключается в том, что DAO можно превратить в промежуточное ПО: превращение xml sql в DAO. Текст запроса:
Map<String, Object> args = new HashMap<>();
args.put("id", 22);
//xsqlid = @{sqlid} = @{namespace}.{id}
User user = db.mapper("@demo.dso.db.getUserById", args);
Это первоначальный вид Weed3, и это мой любимый метод. Также это ключевой момент для конкретной кроссплатформенной интеграции.
BaseMapper также реализован внутри db.table(). Это просто несколько строк кода.
Гибкий, эластичный, прямой, может реализовать любой эффект SQL-кода. Очень удобно для разработки бэкенда управления (потому что условия запроса беспорядочны).
Этот интерфейс можно легко интегрировать в движок сценариев JVM (js, groovy, lua, python, ruby) или язык GraalVM.
Интерфейс db.table():
Вставка (INSERT):
Одна запись:
User user = new User();
...
// Одна запись
db.table("user").set("name", "noear").insert();
db.table("user").setEntity(user).insert();
db.table("user").setEntityIf(user, (k, v) -> v != null).insert(); // Фильтрация null
// Пакетная вставка
db.table("user").insertList(list);
Удаление (DELETE):
// Удалить записи с id < 12
db.table("user").whereLt("id", 12).delete();
Обновление (UPDATE):
Изменить поле sex для записей с id = 23:
// Изменить поле sex записей с id=23
db.table("user").set("sex", 1).whereEq("id", 23).update();
// Добавить или обновить по номеру мобильного телефона
public void saveUser(UserModel m) {
db.talbe("user")
.setEntityIf(m, (k, v) -> v != null)
.upsert("mobile");
}
Выбор (SELECT):
Подсчитать количество записей с id < 100 и длиной имени > 10 символов:
// Подсчёт количества записей с id<100, длина имени>10
db.table("user").where("id<?", 100).and("LENGTH(name)>?", 10).count();
// Выбрать 20 записей с id > 10
db.table("user").whereGte("id", 10).limit(20).selectMapList("*");
// Внутреннее соединение и вывод одной сущности
db.table("user u")
.innerJoin("user_ex e")
.onEq("u.id", "e.user_id")
.whereEq("u.id", 10)
.andEq("e.sex", 1)
.limit(1)
.selectItem("u.*, e.sex user_sex", User.class);
// Запрос ++ (условное построение):
var tb = db.table("user u");
if (test.a) {
tb.innerJoin("user_ext e").onEq("u.id", "e.user_id");
}
if (test.b) {
tb.whereEq("u.id", 1001);
}
tb.selectItem("u.*, e.sex, e.label", User.class);
// Запрос ++2 (построение функции):
db.table("user u")
.build(tb -> {
if (test.a) {
tb.innerJoin("user_ext e").onEq("u.id", "e.user_id");
}
if (test.b) {
tb.whereEq("u.id", 1001);
}
})
.selectItem("u.*, e.sex, e.label", User.class);
Обладает фильтрующей способностью: whereIf, andIf, orIf, setIf, setMapIf, setEntityIf.
Если есть имя, добавить условие имени; (полезно для бэкенда, экономит много if):
// Если есть имя, добавляем условие имени
db.talbe("user").whereIf(name != null, "name=?", name).limit(10).selectMapList("*");
// Вставка, фильтрация null
db.table("user").setMapIf(map, (k, v) -> v != null).insert(); // фильтрация null
// Обновление
db.table("user")
.setIf(name != null, "name", name)
.setIf(sex > 0, "sex", sex)
.setIf(mobile != null && mobile.length() = 11, "mobile", mobile)
.where("id=?", id)
.update();
db.call(), предоставляет операции вызова:
Вызов процедуры:
User user = db.call("user_get").set("id", 1).getItem(User.class);
Вызов SQL:
//@Sql реализуется внутри
//
User user = db.call("select * from user where id=@{id}").set("id", 1).getItem(User.class);
Xmlsql вызов:
// Начинается с @, за которым следует sqlid
//
User user = db.call("@demo.dso.db.getUser").set("id", 1).getItem(User.class);
db.sql(), предоставляет ручные операции SQL:
// В конечном итоге все интерфейсы преобразуются в db.sql(), который является самым нижним интерфейсом
//
User user = db.sql("select * from user where id=?", 12).getItem(User.class);
Long total = db.sql("select count(*) from user").getValue(0l);
// Быстрый вариант db.sql(): db.exe(), используется для быстрой пакетной обработки
//
db.exe("delete from user where id=12");
db.exe("update user sex=1 where id=12");
BaseMapper интерфейс:
Long insert(T entity, boolean excludeNull);
void insertList(List<T> list);
Integer deleteById(Object id);
Integer deleteByIds(Iterable idList);
Integer deleteByMap(Map<String, Object> columnMap);
Integer delete(Act1<WhereQ> condition);
Integer updateById(T entity, boolean excludeNull);
Integer update(T entity, boolean excludeNull, Act1<WhereQ> condition);
Long upsert(T entity, boolean excludeNull);
Long upsertBy(T entity, boolean excludeNull, String conditionFields);
boolean existsById(Object id);
boolean exists(Act1<WhereQ> condition);
T selectById(Object id);
List<T> selectByIds(Iterable idList);
List<T> selectByMap(Map<String, Object> columnMap);
T selectItem(T entity);
T selectItem(Act1<WhereQ> condition);
Map<String, Object> selectMap(Act1<WhereQ> condition);
Object selectValue(String column, Act1<WhereQ> condition);
Long
selectCount(Act1 condition);
List selectList(Act1 condition);
List<Map<String, Object>> selectMapList(Act1 condition);
List selectArray(String column, Act1 condition);
List selectPage(int start, int end, Act1 condition);
List<Map<String, Object>> selectMapPage(int start, int end, Act1 condition);
(二) annotation sql
ICacheServiceEx cache = new LocalCache().nameSet("cache");
//顺带加了缓存
@Sql(value="select * from user where id=@{id}", caching="cache")
public UserModel getUser(int id);
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sql {
String value() default ""; //代码
String caching() default ""; //缓存服务名称
String cacheClear() default ""; //缓存清除标签
String cacheTag() default ""; //缓存标签
int usingCache() default 0; //缓存时间
}
(三) Xml sql
Пример:
<?xml version="1.0" encoding="utf-8" ?>
<mapper namespace="weed3demo.xmlsql2">
<sql id="getUser" :return="demo.model.UserModel" :note="获取用户信息">
SELECT * FROM user WHERE id = @{id:int}
</sql>
</mapper>
Синтаксис:
mapper 开始标签
namespace (属性:命名空间,{namespace}.{id} = sqlid;不包括文件名)
import(属性:导入包或类,多个以;号隔开。可以简化后面的模型写法,或引入工具类)
baseMapper(属性:扩展BaseMapper 的模型,效果:BaseMapper<Xxx>)
db(属性:关联 dataSource bean;效果:@Db("xxx"))
sql 代码块定义指令
id
param?(属性:外部输入变量申明;默认会自动生成)
declare?(属性:内部变量类型预申明)
return(属性:返回类型)
remarks(属性:描述、摘要)
caching?(属性:缓存服务name) //是对 ICacheController 接口的映射
cacheClear?(属性:清除缓存)
cacheTag?(属性:缓存标签,支持在入参或结果里取值替换)
usingCache?(属性:缓存时间,int)
if 判断控制指令(没有else)
test (属性:判断检测代码)
//xml避免语法增强:
//lt(<) lte(<=) gt(>) gte(>=) and(&&) or(||)
//例:m.sex gte 12 :: m.sex >=12
//简化语法增强:
//??(非null,var!=null) ?!(非空字符串, Utils.isNoteEmpty(var))
//例:m.icon?? ::m.icon!=null
//例:m.icon?! ::Utils.isNoteEmpty(m.icon)
for 循环控制指令 (通过 ${var}_index 可获得序号,例:m_index)
var (属性:循环变量申明)
items (属性:集合变量名称)
sep? (属性:分隔符)
trim 修剪指令
trimStart(属性:开始位去除)
trimEnd(属性:结尾位去除)
prefix(属性:添加前缀)
suffix(属性:添加后缀)
ref 引用代码块指令
sql (属性:代码块id)
name:type = 变量申明(可用于属性::param, :declare,var,或宏定义 @{..},${..})
@{name:type?} = 变量注入
${name:type?} = 变量替换,即成为字符串的一部份
//列表([]替代<>)
return="List[weed3demo.mapper.UserModel]" => List<UserModel>
return="List[String]" => List<String> (Date,Long,...大写开头的单值类型)
return="MapList" => List<Map<String,Object>>
return="DataList" => DataList
//一行
return="weed3demo.mapper.UserModel" => UserModel
return="Map" => Map<String,Object>
return="DataItem" => DataItem
//单值
return="String" => String (任何单职类型)
Четыре. Table 语法
(一) 条件操作 (与Mapper共享)
方法 | 效果说明 |
---|---|
where, whereIf | |
whereEq, whereNeq | ==, != |
whereLt, whereLte | <, <= |
whereGt, whereGte | >, >= |
whereLk, whereNlk | LIKE, NOT LIKE |
whereIn, whereNin | IN(..), NOT IN(..) |
whereBtw, whereNbtw | BETWEEN, NOT BETWEEN |
and系统方法 | 同where |
or系统方法 | 同where |
begin | ( |
end | ) |
(二) 表操作 (Table独占)
方法 | 效果说明 |
---|---|
set, setIf | 设置值 |
setMap, setMapIf | 设置值 |
setEntity, setEntityIf | 设置值 |
table | 主表 |
innerJoin, leftJoin, rightJoin | 关联表 |
on, onEq | 关联条件 |
orderBy, orderByAsc, orderByDesc | 排序 |
groupBy | 组 |
having | 组条件 |
limit | 限制范围 |
select | 查询(返回IQuery) |
count | 查询快捷版,统计数量 |
exists | 查询快捷版,是否存在 |
update | 更新 |
insert | 插入 |
delete | 删除 |
(三) IQuery接口
long getCount() throws SQLException;
Object getValue() throws SQLException;
<T> T getValue(T def) throws SQLException;
Variate getVariate() throws SQLException;
<T> T getItem(Class<T> cls) throws SQLException;
<T> List<T> getList(Class<T> cls) throws SQLException;
DataList getDataList() throws SQLException;
DataItem getDataItem() throws SQLException;
List<Map<String,Object>> getMapList() throws SQLException;
Map<String,Object> getMap() throws SQLException;
<T> List<T> getArray(String column) throws SQLException;
<T> List<T> getArray(int columnIndex)
В запросе представлен текст на английском языке, который содержит фрагменты кода и технические термины из области разработки программного обеспечения. Для точного перевода необходимо больше контекста. Пять. Кэш и транзакции
ICacheServiceEx cache = new LocalCache().nameSet("cache");
User user = db.table("user")
.whereEq("id",12)
.caching(cache) // Кэширование, время по умолчанию для кэша
.selectItem("*", User.class);
// При запросе кеш
User user = db.table("user")
.whereGt("id", 12)
.limit(100, 20) // Пейджинговый запрос
.caching(cache)
.usingCache(60 * 5) // Кэшировать на 5 минут
.cacheTag("user_all") // Добавить метку кэша user_all
.selectList("*", User.class);
// Обновить и очистить кэш // При следующем запросе можно получить последние данные
db.table("user").set("sex", 0).whereEq("id", 101).update();
cache.clear("user_all");
Trans.tran(()->{
// Регистрация пользователя
long user_id = userDao.addUser(user);
// После регистрации отправить 10 золотых монет (завершить в той же транзакции)
userDao.addUserGold(user_id, 10);
});
Trans.tran(() -> {
// Система пользователей, добавление пользователя и золотых монет
user.id = userDao.addUser(user); // id увеличивается автоматически
// Банковская система
bankDao.addAccount(user.id); // Создание учётной записи
bankDao.addAccountGold(user.id, 10); // Добавление золотых монет на счёт
bankDao.addJournal(user.id,10); // Добавить запись в журнал
// Распространение сообщений // Для последующего горизонтального расширения бизнеса
MsgUtil.sendMessage("user.registered",user.value);
});
Шесть. Мониторинг и запись
WeedConfig.onException((cmd,ex)->{
// Можно сделать запись
ex.printStackTrace();
});
WeedConfig.onExecuteAft((cmd)->{
//cmd.timespan() // Получить время выполнения (в миллисекундах)
});
WeedConfig.onLog((cmd) -> {
if (cmd.isLog >= 0) { //isLog: -1, не нужно записывать; 0, по умолчанию; 1, нужно записать
//cmd.text; // Выполняемый код
//cmd.paramS; // Параметры выполнения
//cmd.paramMap(); // Параметры выполнения в виде карты
}
});
// Пример: запретить операцию DELETE
WeedConfig.onExecuteBef((cmd)->{
if(cmd.text.indexOf("DELETE ") >=0){
return false;
}
return true;
});
Семь. Встраивание в JVM-скрипты
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine _eng = scriptEngineManager.getEngineByName("nashorn");
Invocable _eng_call = (Invocable)_eng;
_eng.put("db", db);
/*
* var map = db.table("user").where('id=?',1).getMap();
* var user_id = map.id;
*/
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine _eng = scriptEngineManager.getEngineByName("groovy");
Invocable _eng_call = (Invocable)_eng;
_eng.put("db", db);
/*
* def map = db.table("user").where('id=?',1).getMap();
* def user_id = map.id;
*/
Восемь. Синтаксис
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.