field) {
29 | return (F) get(field.shortName);
30 | }
31 |
32 | @Override
33 | public String toString() {
34 | return super.toString();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/annotation/cache.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.annotation;
2 |
3 | import cn.liberg.core.TenThousands;
4 |
5 | /**
6 | * DAO中列缓存相关的设置
7 | * cap()>0时,会在本列进行缓存
8 | * 指定group和groupCap>0,也会在组上进行缓存
9 | */
10 | public @interface cache {
11 | /**
12 | * 缓存容量,默认为0——即该列上不进行数据缓存
13 | *
14 | * 若cap()为0,必须指定group()在group上进行缓存,否则没有意义
15 | */
16 | public int cap() default 0;
17 |
18 | /**
19 | * 如果多个列值的组合具有唯一性,可归为一组
20 | * 组名可以用"g1","g2",...
21 | */
22 | public String group() default "";
23 |
24 | /**
25 | * 如果多个列值的组合具有唯一性,可归为一组
26 | * 组内的缓存容量
27 | * 同组内多个列均指定了groupCap(),则取最大值
28 | */
29 | public int groupCap() default TenThousands.X1;
30 |
31 | /**
32 | * 同一组内,seq值更小的列,排在更前面
33 | * 若seq相同,则在实体类中先出现的列排在更前面
34 | */
35 | public int seq() default 0;
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/annotation/dbmap.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * 用于实体类,或实体类成员变量的注解
10 | *
11 | * @author Liberg
12 | */
13 | @Retention(RetentionPolicy.SOURCE)
14 | @Target({ElementType.TYPE, ElementType.FIELD})
15 | public @interface dbmap {
16 | /**
17 | * 该注解被用于实体类时,若isMap()为false,则该实体类不映射为数据库的表
18 | * 同理,被用于实体类的成员时,若isMap()为false,改成员不映射到数据表的列
19 | */
20 | public boolean isMap() default true;
21 |
22 | /**
23 | * 列的存储长度,String类型有效
24 | * 长度小于4096,映射为数据库VARCHAR列
25 | * 长度达到/超过4096时,映射为数据库TEXT列
26 | */
27 | public int length() default 0;
28 | /**
29 | * 是否为字段创建索引
30 | */
31 | public boolean isIndex() default false;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/UpdateMessageDigestInputStream.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.security.MessageDigest;
6 |
7 | abstract class UpdateMessageDigestInputStream extends InputStream {
8 | UpdateMessageDigestInputStream() {
9 | }
10 |
11 | public void updateMessageDigest(MessageDigest messageDigest) throws IOException {
12 | int data;
13 | while((data = this.read()) != -1) {
14 | messageDigest.update((byte)data);
15 | }
16 |
17 | }
18 |
19 | public void updateMessageDigest(MessageDigest messageDigest, int len) throws IOException {
20 | int data;
21 | for(int bytesRead = 0; bytesRead < len && (data = this.read()) != -1; ++bytesRead) {
22 | messageDigest.update((byte)data);
23 | }
24 |
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/IdMaker.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.atomic.AtomicLong;
5 |
6 | /**
7 | * 带前缀的唯一标识生成器
8 | * 可用于生成uid、token
9 | *
10 | * @author Liberg
11 | */
12 | public class IdMaker {
13 | private String prefix = "";
14 | private Random rand = new Random();
15 | private AtomicLong sn = new AtomicLong(0);
16 |
17 | public void setPrefix(String newPrefix) {
18 | prefix = newPrefix;
19 | }
20 |
21 | public IdMaker() {
22 | }
23 |
24 | public IdMaker(String prefix) {
25 | this.prefix = prefix;
26 | }
27 |
28 | public String nextTempId() {
29 | return System.currentTimeMillis() + "_" + rand.nextInt(100000);
30 | }
31 |
32 | public String nextUid() {
33 | return prefix + nextTempId() + "_" + sn.incrementAndGet();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/entity/User.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data.entity;
2 |
3 | import cn.liberg.core.TenThousands;
4 | import cn.liberg.annotation.cache;
5 | import cn.liberg.annotation.dbmap;
6 |
7 | public class User {
8 | @cache(cap = TenThousands.X1)
9 | public long id;
10 | @dbmap(isIndex = true, length = 63) @cache(cap = 4, group = "g1", groupCap = 6)
11 | public String name;
12 | public String password;
13 | public byte age;
14 | public long roleId;
15 | @cache(group = "g1", groupCap = 6)
16 | public long createTime;
17 | @dbmap(isMap=false)
18 | public Role role;
19 |
20 | @Override
21 | public String toString() {
22 | return "User{" +
23 | "id=" + id +
24 | ", name='" + name + '\'' +
25 | ", password='" + password + '\'' +
26 | ", age=" + age +
27 | ", createTime=" + createTime +
28 | '}';
29 | }
30 | }
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/IDataBaseConf.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | /**
4 | * 数据库配置接口
5 | *
6 | * @author Liberg
7 | * @see IDataBase
8 | */
9 | public interface IDataBaseConf {
10 |
11 | /**
12 | * jdbc driver类名称,eg:"com.mysql.cj.jdbc.Driver"
13 | * @return
14 | */
15 | String getDriverName();
16 |
17 | /**
18 | * 数据库名称
19 | * @return
20 | */
21 | String getDbName();
22 |
23 | /**
24 | * 数据库连接url,eg:"jdbc:mysql://localhost:3306/"
25 | * @return
26 | */
27 | String getUrl();
28 |
29 | /**
30 | * 访问数据库服务的用户名,eg:"root"
31 | * @return
32 | */
33 | String getUserName();
34 |
35 | /**
36 | * 访问数据库服务的密码
37 | * @return
38 | */
39 | String getPassword();
40 |
41 | /**
42 | * 符编码类型,eg:"utf8"
43 | * @return 字
44 | */
45 | String getCharset();
46 |
47 | /**
48 | * 排序规则,eg:"utf8_general_ci"
49 | * @return
50 | */
51 | String getCollation();
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/OperatorException.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | /**
4 | * 通用的异常类型,除了cause为一个Throwable外,
5 | * 还包含一个错误状态码{@link IStatusCode}
6 | *
7 | * @author Liberg
8 | * @see IStatusCode
9 | */
10 | public class OperatorException extends Exception {
11 | private IStatusCode sc = StatusCode.ERROR_SERVER;
12 |
13 | public OperatorException(IStatusCode sc) {
14 | super();
15 | this.sc = sc;
16 | }
17 |
18 | public OperatorException(IStatusCode sc, Throwable throwable) {
19 | super(throwable);
20 | this.sc = sc;
21 | }
22 |
23 | public OperatorException(IStatusCode sc, String message) {
24 | super(message);
25 | this.sc = sc;
26 | }
27 |
28 | public OperatorException(Throwable throwable) {
29 | super(throwable);
30 | }
31 |
32 | public IStatusCode statusCode() {
33 | return sc;
34 | }
35 |
36 | public int code() {
37 | return sc.code();
38 | }
39 |
40 | public String desc() {
41 | return sc.desc();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 liberg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/database/query/JoinQueryTest.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.query;
2 |
3 | import cn.liberg.core.OperatorException;
4 | import cn.liberg.database.join.JoinQuery;
5 | import cn.liberg.support.data.dao.RoleDao;
6 | import cn.liberg.support.data.dao.UserDao;
7 | import org.junit.Test;
8 |
9 | import static org.junit.Assert.assertEquals;
10 |
11 | public class JoinQueryTest {
12 |
13 | public static JoinQuery testJoinQuery() throws OperatorException {
14 | UserDao userDao = UserDao.self();
15 | RoleDao roleDao = RoleDao.self();
16 | JoinQuery jq = JoinQuery.basedOn(userDao)
17 | .innerJoin(roleDao).eq(userDao.columnRoleId, roleDao.columnId)
18 | .where(userDao).eq(userDao.columnName, "张三")
19 | .asc(userDao.columnId).limit(10);
20 | System.out.println(jq.build());
21 | assertEquals(jq.build(), "from user a inner join role b on a._role_id=b.id where a._name='张三' order by a.id limit 10");
22 | return jq;
23 | }
24 |
25 | @Test
26 | public void testBuild() throws OperatorException {
27 | testJoinQuery();
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/join/JoinOn.java:
--------------------------------------------------------------------------------
1 |
2 | package cn.liberg.database.join;
3 |
4 |
5 | public class JoinOn {
6 | public static final String TYPE_INNER = "inner join";
7 | public static final String TYPE_LEFT = "left join";
8 | public static final String TYPE_RIGHT = "right join";
9 |
10 | //第一张表
11 | private JoinDao acc1;
12 | //join类型
13 | private final String type;
14 | //第二张表
15 | public final JoinDao acc2;
16 | //join的条件
17 | public final JoinWhere joinWhere;
18 |
19 | public JoinOn(String joinType, JoinDao acc1, JoinDao acc2) {
20 | this.acc1 = acc1;
21 | this.acc2 = acc2;
22 | this.type = joinType;
23 | joinWhere = new JoinWhere();
24 | }
25 |
26 | public JoinWhere getJoinWhere() {
27 | return joinWhere;
28 | }
29 |
30 | public String build() {
31 | StringBuilder sb = new StringBuilder();
32 | sb.append(type + " ");
33 | sb.append(acc2.dao.getTableName()+" ");
34 | sb.append(acc2.alias);
35 | sb.append(" on ");
36 | sb.append(joinWhere.build());
37 | return sb.toString();
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/join/JoinFields.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.join;
2 |
3 | public class JoinFields {
4 | private StringBuilder sb = new StringBuilder();
5 |
6 | private JoinFields() {}
7 |
8 | public static JoinFields of(JoinDao dao, String[] fields) {
9 | JoinFields jf = new JoinFields();
10 | jf.add(dao, fields);
11 | return jf;
12 | }
13 |
14 | public static JoinFields of(JoinDao dao) {
15 | JoinFields jf = new JoinFields();
16 | jf.addAll(dao);
17 | return jf;
18 | }
19 |
20 |
21 | public void add(JoinDao dao, String[] fields) {
22 | String a = dao.alias;
23 | for(String f : fields) {
24 | sb.append(a + "." + f);
25 | sb.append(" as " + a+f);
26 | sb.append(",");
27 | }
28 | }
29 |
30 | public void addAll(JoinDao dao) {
31 | String a = dao.alias;
32 | sb.append(a + ".*");
33 | sb.append(",");
34 | }
35 |
36 | public String build() {
37 | if(sb.charAt(sb.length()-1) == ',') {
38 | sb.setCharAt(sb.length()-1, ' ');
39 | }
40 | return sb.toString();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/Joints.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | /**
4 | * 定义查询子条件{@link Condition}的各种连接方式。
5 | *
6 | * {@link Joints}和{@link Condition}一起组合出完整的查询条件。
7 | *
8 | * @author Liberg
9 | * @see Condition
10 | */
11 | public class Joints {
12 |
13 | public static final WhereMeta AND = new LogicalOperator(WhereMeta.AND);
14 | public static final WhereMeta OR = new LogicalOperator(WhereMeta.OR);
15 | public static final WhereMeta NOT = new LogicalOperator(WhereMeta.NOT);
16 |
17 | public static final WhereMeta BRACKET_START = new WhereMeta(WhereMeta.BRACKET_START) {
18 | @Override
19 | public boolean isStartBracket() {
20 | return true;
21 | }
22 | };
23 | public static final WhereMeta BRACKET_END = new WhereMeta(WhereMeta.BRACKET_END) {
24 | @Override
25 | public boolean isEndBracket() {
26 | return true;
27 | }
28 | };
29 |
30 | private static class LogicalOperator extends WhereMeta {
31 | public LogicalOperator(String op) {
32 | super(op);
33 | }
34 |
35 | @Override
36 | public boolean isLogicalOperator() {
37 | return true;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/DBConfig.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data;
2 |
3 | import cn.liberg.database.IDataBaseConf;
4 |
5 | public class DBConfig implements IDataBaseConf {
6 | private String driverName = "com.mysql.cj.jdbc.Driver";
7 | private String url = "jdbc:mysql://localhost:3306/";
8 | private String dbName = "liberg_test";
9 | private String userName = "root";
10 | private String password = "";
11 | private String charset = "utf8";
12 | private String collation = "utf8_general_ci";
13 |
14 |
15 | @Override
16 | public String getDriverName() {
17 | return driverName;
18 | }
19 |
20 | @Override
21 | public String getDbName() {
22 | return dbName;
23 | }
24 |
25 | @Override
26 | public String getUrl() {
27 | return url;
28 | }
29 |
30 | @Override
31 | public String getUserName() {
32 | return userName;
33 | }
34 |
35 | @Override
36 | public String getPassword() {
37 | return password;
38 | }
39 |
40 | @Override
41 | public String getCharset() {
42 | return charset;
43 | }
44 |
45 | @Override
46 | public String getCollation() {
47 | return collation;
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/SelectColumn.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Field;
4 | import cn.liberg.database.BaseDao;
5 |
6 | import java.sql.ResultSet;
7 | import java.sql.SQLException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * 列查询
13 | *
14 | * @param 泛型参数,代表查询列的数据类型
15 | *
16 | * @author Liberg
17 | */
18 | public class SelectColumn extends Select {
19 | private Field column;
20 |
21 | public SelectColumn(BaseDao dao, Field column) {
22 | super(dao);
23 | this.column = column;
24 | }
25 |
26 | @Override
27 | protected void appendColumnsTo(StringBuilder sql) {
28 | sql.append(column.name);
29 | }
30 |
31 | @Override
32 | public F readOne(ResultSet resultSet) throws SQLException {
33 | if(resultSet.next()) {
34 | return column.getValue(resultSet, 1);
35 | } else {
36 | return null;
37 | }
38 | }
39 |
40 | @Override
41 | public List readAll(ResultSet resultSet) throws SQLException {
42 | List list = new ArrayList<>();
43 | while (resultSet.next()) {
44 | list.add(column.getValue(resultSet, 1));
45 | }
46 | return list;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/cache/ConcurrentLRUCache.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.cache;
2 |
3 | /**
4 | * 线程安全的,
5 | * 基于{@link cn.liberg.cache.LRUCache}、实现{@link ICache}接口的LRU缓存
6 | * @param
7 | * @param
8 | *
9 | * @author Liberg
10 | */
11 | public class ConcurrentLRUCache implements ICache {
12 |
13 | private final LRUCache lruCache;
14 |
15 | /**
16 | * @param capacity 指定缓存的容量限制
17 | */
18 | public ConcurrentLRUCache(int capacity) {
19 | lruCache = new LRUCache<>(capacity);
20 | }
21 |
22 | @Override
23 | public synchronized V get(K key) {
24 | return lruCache.get(key);
25 | }
26 |
27 | @Override
28 | public synchronized void put(K key, V obj) {
29 | lruCache.put(key, obj);
30 | }
31 |
32 | @Override
33 | public synchronized void remove(K key) {
34 | lruCache.remove(key);
35 | }
36 |
37 | @Override
38 | public void clear() {
39 | lruCache.clear();
40 | }
41 |
42 | @Override
43 | public int size() {
44 | return lruCache.size();
45 | }
46 |
47 | @Override
48 | public int capacity() {
49 | return lruCache.capacity;
50 | }
51 |
52 | @Override
53 | public String toString() {
54 | return lruCache.toString();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/join/JoinCondition.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.join;
2 |
3 | import cn.liberg.database.WhereMeta;
4 |
5 | class JoinCondition extends WhereMeta {
6 | /**
7 | * join时第一张表
8 | */
9 | JoinDao dao1;
10 | /**
11 | * join时第二张表,
12 | * 若为null,则表示value是一个跟第二张表没关系的数据
13 | */
14 | JoinDao dao2;
15 |
16 |
17 | public JoinCondition(JoinDao dao1, JoinDao dao2, String name, String link, String value) {
18 | super(build(dao1, dao2, name, link, value));
19 | this.dao1 = dao1;
20 | this.dao2 = dao2;
21 | }
22 |
23 | public JoinCondition(JoinDao dao1, String name, String link, String value) {
24 | super(build(dao1, null, name, link, value));
25 | this.dao1 = dao1;
26 | this.dao2 = null;
27 | }
28 |
29 | @Override
30 | public boolean isCondition() {
31 | return true;
32 | }
33 |
34 | private static String build(JoinDao dao1, JoinDao dao2, String name, String link, String value) {
35 | StringBuilder sb = new StringBuilder(dao1.alias + ".");
36 | sb.append(name);
37 | sb.append(link);
38 | if (dao2 != null) {
39 | sb.append(dao2.alias + ".");
40 | }
41 | sb.append(value);
42 | return sb.toString();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/Column.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | /**
4 | * Column类定义数据表的列和entity字段的映射绑定关系。
5 | *
6 | * 实体字段名支持以字母m(member、成员变量)开头的形式,比如mPackage;
7 | * 数据表的列名除了id特殊外,其他列均以下划线_开头,以防列名跟sql关键字冲突。
8 | * 详见{@link Column#parseColumnName}方法的实现。
9 | *
10 | *
需要指出的是,entity中指定了{@code @dbmap(isIndex=false)}注解的字段不会建立映射关系。
11 | *
12 | *
为了保持XxxDao更“轻量级”,实体中的字段除了名称外,其他信息并没有记录到相应的{@code Column}中,
13 | * 而是在构造建表语句时,由{@link cn.liberg.database.TableBuilder#add}方法中进行传入。
14 | * 传入的信息包括: 类型、长度、是否是索引字段、列注释等。
15 | *
16 | * 子类包括:
17 | * {@link ByteColumn}
18 | * {@link IntegerColumn}
19 | * {@link LongColumn}
20 | * {@link StringColumn}
21 | * {@link IdColumn}
22 | *
23 | * @param 与数据表对应的entity实体类型
24 | * @param 数据列的类型
25 | * @author Liberg
26 | * @see cn.liberg.database.TableBuilder
27 | * @see ByteColumn
28 | * @see IntegerColumn
29 | * @see LongColumn
30 | * @see StringColumn
31 | * @see IdColumn
32 | */
33 | public abstract class Column extends Field {
34 |
35 | public Column(String fieldName, String shortName) {
36 | super(fieldName, shortName);
37 | }
38 |
39 | /**
40 | * 从实体对象entity中读取列的值
41 | */
42 | public abstract F get(E entity);
43 |
44 | /**
45 | * 将值设置到实体对象entity中
46 | */
47 | public abstract void set(E entity, F value);
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/PreparedSelectColumn.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Field;
4 | import cn.liberg.database.BaseDao;
5 |
6 | import java.sql.ResultSet;
7 | import java.sql.SQLException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * Prepare方式select数据表部分列的入口类。
13 | *
14 | * 继承自{@link PreparedSelect}
15 | *
16 | * @author Liberg
17 | * @see PreparedSelect
18 | */
19 | public class PreparedSelectColumn extends PreparedSelect {
20 | private Field field;
21 |
22 | public PreparedSelectColumn(BaseDao dao, Field column) {
23 | super(dao);
24 | this.field = column;
25 | }
26 |
27 | @Override
28 | protected void appendColumns(StringBuilder sb) {
29 | sb.append(field.name);
30 | }
31 |
32 |
33 | @Override
34 | protected F readOne(ResultSet resultSet) throws SQLException {
35 | if(resultSet.next()) {
36 | return field.getValue(resultSet, 1);
37 | } else {
38 | return null;
39 | }
40 | }
41 |
42 | @Override
43 | protected List readAll(ResultSet resultSet) throws SQLException {
44 | List list = new ArrayList<>();
45 | while (resultSet.next()) {
46 | list.add(field.getValue(resultSet, 1));
47 | }
48 | return list;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/cache/LRUCache.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.cache;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * 非线程安全的LRU(Least Recently Used)缓存实现
8 | * 基于{@link java.util.LinkedHashMap}
9 | *
10 | * @param
11 | * @param
12 | *
13 | * @author Liberg
14 | */
15 | public class LRUCache extends LinkedHashMap {
16 | public LRUCache(int capacity) {
17 | super(16, 0.75F, true);
18 | this.capacity = capacity;
19 | }
20 |
21 | @Override
22 | protected boolean removeEldestEntry(Map.Entry eldest) {
23 | return size() > capacity;
24 | }
25 |
26 | protected final int capacity;
27 |
28 | @Override
29 | public String toString() {
30 | final int count = Math.min(size(), 10);
31 | StringBuilder sb = new StringBuilder();
32 | int i = 0;
33 | for(Map.Entry entry : entrySet()) {
34 | i++;
35 | sb.append(entry.getKey());
36 | sb.append("=");
37 | sb.append(entry.getValue());
38 | if(i >= count) {
39 | break;
40 | } else {
41 | sb.append(",");
42 | }
43 | }
44 | if(size() > count) {
45 | sb.append(",...");
46 | sb.append(size()-count);
47 | sb.append("more");
48 | }
49 | return this.getClass().getSimpleName() + "{" +
50 | "capacity=" + capacity +
51 | ", entries=[" + sb.toString() + "]}";
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/SqlDefender.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import java.util.regex.Matcher;
4 | import java.util.regex.Pattern;
5 |
6 | /**
7 | * 防Sql注入辅助类
8 | *
9 | * @author Liberg
10 | */
11 | public class SqlDefender {
12 | public static final Pattern emojiPattern = Pattern.compile(
13 | "[\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\u2600-\u27ff]",
14 | Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE);
15 |
16 | /**
17 | * 过滤类型支持:Byte,Integer,Long,String
18 | * @param value
19 | * @return
20 | */
21 | public static String format(Object value) {
22 | if (value != null) {
23 | if (value instanceof Number) {
24 | return value.toString();
25 | } else {
26 | // 过滤String类型的数据,防止sql注入
27 | return format(value.toString());
28 | }
29 | }
30 | return null;
31 | }
32 |
33 | public static String format(String value) {
34 | if(value == null) {
35 | return null;
36 | } else {
37 | String result = value.replace("\\", "\\\\")
38 | .replace("'", "\\'");
39 | return '\'' + result + '\'';
40 | }
41 | }
42 |
43 | public static String filterEmoji(String source) {
44 | if (source != null) {
45 | Matcher emojiMatcher = emojiPattern.matcher(source);
46 | if (emojiMatcher.find()) {
47 | source = emojiMatcher.replaceAll("");
48 | }
49 | }
50 | return source;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/RandomString.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support;
2 |
3 | import java.util.Random;
4 |
5 | public class RandomString {
6 | private static char[] NUMBERS = "0123456789".toCharArray();
7 | private static char[] LETTERS = "abcdefghijklmnopqrstuvwxyz".toCharArray();
8 | private static char[] CHARS = new char[NUMBERS.length+ LETTERS.length];
9 | private int minLength = 0;
10 | private int maxLength = 1000;
11 | private Random random = new Random();
12 |
13 | public RandomString(int minLength, int maxLength) {
14 | this.minLength = minLength;
15 | this.maxLength = maxLength;
16 | }
17 |
18 | static {
19 | for (int i = 0; i < NUMBERS.length; i++) {
20 | CHARS[i] = NUMBERS[i];
21 | }
22 | for (int i = 0; i < LETTERS.length; i++) {
23 | CHARS[NUMBERS.length+i] = LETTERS[i];
24 | }
25 | }
26 |
27 | public String next() {
28 | int length = minLength + random.nextInt(maxLength-minLength+1);
29 | char[] array = new char[length];
30 | for (int i = 0; i < array.length; i++) {
31 | array[i] = CHARS[random.nextInt(CHARS.length)];
32 | }
33 | return new String(array);
34 | }
35 |
36 | public String next(int minLength, int maxLength) {
37 | int length = minLength + random.nextInt(maxLength-minLength+1);
38 | char[] array = new char[length];
39 | for (int i = 0; i < array.length; i++) {
40 | array[i] = CHARS[random.nextInt(CHARS.length)];
41 | }
42 | return new String(array);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/PeriodicThread.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | /**
4 | * 对线程的简单封装,按照某个时间间隔{@code intervalMillis},
5 | * 周期性地执行任务{@code runnable}
6 | *
7 | * @author Liberg
8 | */
9 | public class PeriodicThread {
10 | protected Thread thread;
11 | private boolean running = false;
12 | private final Runnable runnable;
13 | private int intervalMillis;
14 | private String name;
15 |
16 | /**
17 | * @param name 为线程指定一个有区分度的名字
18 | * @param runnable 周期性执行的runnable
19 | * @param intervalMillis 调度间隔ms时间
20 | */
21 | public PeriodicThread(String name, Runnable runnable, int intervalMillis) {
22 | this.runnable = runnable;
23 | this.intervalMillis = intervalMillis;
24 | this.name = name;
25 | thread = initThread(name);
26 | }
27 |
28 | public void setIntervalMillis(int intervalMillis) {
29 | this.intervalMillis = intervalMillis;
30 | }
31 |
32 | public void start() {
33 | if(thread == null) {
34 | throw new RuntimeException("PeriodicThread["+name+"] has been destroyed.");
35 | }
36 | running = true;
37 | thread.start();
38 | }
39 |
40 | public void destroy() {
41 | running = false;
42 | thread = null;
43 | }
44 |
45 | private Thread initThread(String name) {
46 | return new Thread(()->{
47 | while (running) {
48 | runnable.run();
49 | try {
50 | Thread.sleep(intervalMillis);
51 | } catch (InterruptedException e) { }
52 | }
53 | }, name);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/DBUpgrader.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data;
2 |
3 | import cn.liberg.database.IDataBaseConf;
4 | import cn.liberg.database.TableAlteration;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.lang.reflect.Method;
9 | import java.sql.SQLException;
10 | import java.sql.Statement;
11 |
12 | public class DBUpgrader extends DBImpl {
13 | private static final Logger logger = LoggerFactory.getLogger(DBUpgrader.class);
14 |
15 | public DBUpgrader(IDataBaseConf dbConf) {
16 | super(dbConf);
17 | }
18 |
19 | public int upgrade(Statement stat, int dbVersion, int newVersion) throws SQLException {
20 | Class extends DBUpgrader> clazz = this.getClass();
21 | String clazzName = clazz.getSimpleName();
22 | int result = dbVersion;
23 | for (int i = dbVersion + 1; i <= newVersion; i++) {
24 | try {
25 | Method method = clazz.getDeclaredMethod("upgradeTo" + i, Statement.class);
26 | method.invoke(this, stat);
27 | result = i;
28 | } catch(NoSuchMethodException e) {
29 | //skip
30 | logger.warn(clazz.getSimpleName()+" upgradeTo" + i + ": no such method.");
31 | } catch (Exception e) {
32 | logger.error(clazzName+" failed:" + super.getName() +
33 | ". version=" + result + ", expectedVersion=" + newVersion, e);
34 | break;
35 | }
36 | }
37 | return result;
38 | }
39 |
40 | private TableAlteration alter(String tableName) {
41 | return new TableAlteration(tableName);
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/DBInitializer.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data;
2 |
3 | import cn.liberg.UserDaoTest;
4 | import cn.liberg.core.OperatorException;
5 | import cn.liberg.database.DBHelper;
6 | import cn.liberg.support.data.dao.RoleDao;
7 | import cn.liberg.support.data.dao.UserDao;
8 | import cn.liberg.support.data.entity.Role;
9 | import cn.liberg.support.data.entity.User;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 |
13 | public class DBInitializer {
14 | private static final Logger logger = LoggerFactory.getLogger(DBInitializer.class);
15 |
16 | /**
17 | * 这个方法由DBImpl自动调用,完成建库、建表之后的数据初始化工作
18 | */
19 | public void initData() {
20 | try {
21 | initUsers();
22 | } catch (OperatorException e) {
23 | logger.error("initData error", e);
24 | }
25 | }
26 |
27 | /**
28 | * 这里演示,如何在Liberg框架自动完成建库、建表之后,
29 | * 进行一些初始数据的写入
30 | */
31 | public void initUsers() throws OperatorException {
32 | Role role = new Role();
33 | role.name = "超级管理员";
34 | role.permissions = "all";
35 |
36 | User zhang = new User();
37 | zhang.name = "张三";
38 | zhang.password = UserDaoTest.getMd5WithSalt("123");
39 | zhang.createTime = System.currentTimeMillis();
40 |
41 | DBHelper.self().beginTransact();
42 | try {
43 | long roleId = RoleDao.self().save(role);
44 | zhang.roleId = roleId;
45 | UserDao.self().save(zhang);
46 | DBHelper.self().endTransact();
47 | } catch (Exception e) {
48 | DBHelper.self().transactRollback();
49 | logger.error("initUsers error", e);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/WhereMeta.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | /**
4 | * {@code WhereMeta}抽象出构成{@code where}条件的各种元素。
5 | *
6 | * 分为几类:
7 | * 1、比较类,{@link Condition}
8 | * 2、逻辑运算符类,{@link Joints#AND}/{@link Joints#OR}/{@link Joints#NOT}
9 | * 3、括号类,{@link Joints#BRACKET_START}/{@link Joints#BRACKET_END}
10 | *
11 | * 子类(对象)包括:
12 | * @see Condition
13 | * @see Joints#AND
14 | * @see Joints#OR
15 | * @see Joints#NOT
16 | * @see Joints#BRACKET_START
17 | * @see Joints#BRACKET_END
18 | * @author Liberg
19 | *
20 | */
21 | public class WhereMeta {
22 | public static final String AND = " and ";
23 | public static final String OR = " or ";
24 | public static final String NOT = " not ";
25 | public static final String BRACKET_START = "(";
26 | public static final String BRACKET_END = ")";
27 |
28 | /**
29 | * String值
30 | */
31 | public final String value;
32 |
33 | protected WhereMeta(String value) {
34 | this.value = value;
35 | }
36 |
37 | /**
38 | * 是否是左括号:{@link Joints#BRACKET_START}
39 | *
40 | * @return
41 | */
42 | public boolean isStartBracket() {
43 | return false;
44 | }
45 |
46 | /**
47 | * 是否是右括号:{@link Joints#BRACKET_END}
48 | *
49 | * @return
50 | */
51 | public boolean isEndBracket() {
52 | return false;
53 | }
54 |
55 | /**
56 | * 是否是查询子条件:{@link Condition}
57 | *
58 | * @return
59 | */
60 | public boolean isCondition() {
61 | return false;
62 | }
63 |
64 | /**
65 | * 是否是逻辑运算符:
66 | * {@link Joints#AND}
67 | * {@link Joints#OR}
68 | * {@link Joints#NOT}
69 | *
70 | * @return
71 | */
72 | public boolean isLogicalOperator() {
73 | return false;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/update/UpdateWhere.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.update;
2 |
3 | import cn.liberg.core.OperatorException;
4 | import cn.liberg.database.BaseDao;
5 | import cn.liberg.database.DBHelper;
6 | import cn.liberg.database.select.Where;
7 |
8 | import java.util.List;
9 |
10 | /**
11 | * update操作的执行类。
12 | *
13 | * @param 代表实体类的泛型参数
14 | *
15 | * @author Liberg
16 | * @see Update
17 | */
18 | public class UpdateWhere extends Where> {
19 |
20 | private Update update;
21 |
22 | public UpdateWhere(Update update) {
23 | this.update = update;
24 | }
25 |
26 | private String buildSql(StringBuilder where) {
27 | StringBuilder sql = new StringBuilder();
28 | sql.append("update ");
29 | sql.append(update.dao.getTableName());
30 | sql.append(" set ");
31 | sql.append(update.build());
32 | sql.append(" where ");
33 | sql.append(where);
34 | return sql.toString();
35 | }
36 |
37 | /**
38 | * 如果dao中没有缓存数据,可以放心使用
39 | *
40 | * 如果dao中有缓存数据,更新缓存是很重的操作,甚至可能导致缓存失效,
41 | * 尽量少用。建议使用{@link cn.liberg.database.BaseDao#update(T)}方法
42 | */
43 | public void execute() throws OperatorException {
44 | StringBuilder where = new StringBuilder();
45 | appendConditionTo(where);
46 | // 更新数据库
47 | DBHelper.self().executeSql(buildSql(where));
48 |
49 | final BaseDao dao = update.dao;
50 | if(dao.isEntityCached()) {
51 | // 更新缓存,或者让缓存失效
52 | List list = dao.getPage(where.toString(), 0, 31);
53 | if(list.size() <= 30) {
54 | for(T entity : list) {
55 | dao.putToCache(entity);
56 | }
57 | } else {
58 | dao.clearCache();
59 | }
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/CachedColumn.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import cn.liberg.cache.ConcurrentLRUCache;
4 |
5 | public abstract class CachedColumn extends Column implements ICacheEntity {
6 | final ConcurrentLRUCache lruCache;
7 |
8 | /**
9 | * capacity传入0,则不通过本列作为key缓存查询到的数据
10 | * @param entityFieldName
11 | * @param shortName
12 | * @param capacity
13 | */
14 | public CachedColumn(String entityFieldName, String shortName, int capacity) {
15 | super(entityFieldName, shortName);
16 | if(capacity > 0) {
17 | lruCache = new ConcurrentLRUCache<>(capacity);
18 | } else {
19 | lruCache = null;
20 | }
21 | }
22 |
23 | public CachedColumn(String entityFieldName, String shortName) {
24 | this(entityFieldName, shortName, 0);
25 | }
26 |
27 | public E getFromCache(F key) {
28 | if(lruCache != null) {
29 | return lruCache.get(key);
30 | } else {
31 | return null;
32 | }
33 | }
34 |
35 | public void setToCache(F value, E entity) {
36 | if(lruCache != null) {
37 | lruCache.put(value, entity);
38 | }
39 | }
40 |
41 | @Override
42 | public void put(E entity) {
43 | setToCache(get(entity), entity);
44 | }
45 |
46 | @Override
47 | public void remove(E entity) {
48 | if(lruCache != null) {
49 | lruCache.remove(get(entity));
50 | }
51 | }
52 |
53 | @Override
54 | public void clear() {
55 | if(lruCache != null) {
56 | lruCache.clear();
57 | }
58 | }
59 |
60 | public int size() {
61 | return lruCache == null ? 0 : lruCache.size();
62 | }
63 |
64 | public int capacity() {
65 | return lruCache == null ? 0 : lruCache.capacity();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/PreparedSelectWhere.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.OperatorException;
5 |
6 | /**
7 | * prepare预编译查询方式,查询条件类。
8 | *
9 | * @param
10 | *
11 | * @author Liberg
12 | */
13 | public class PreparedSelectWhere extends PreparedWhere> {
14 |
15 | private final PreparedSelect select;
16 | private Column orderBy = null;
17 | private boolean isAsc = true;
18 |
19 | public PreparedSelectWhere(PreparedSelect select) {
20 | this.select = select;
21 | }
22 |
23 | public PreparedSelectExecutor prepare() throws OperatorException {
24 | return new PreparedSelectExecutor<>(select, this);
25 | }
26 |
27 | /**
28 | * 按指定column升序
29 | */
30 | public PreparedSelectWhere asc(Column column) {
31 | orderBy = column;
32 | isAsc = true;
33 | return this;
34 | }
35 |
36 | /**
37 | * 按指定column降序
38 | */
39 | public PreparedSelectWhere desc(Column column) {
40 | orderBy = column;
41 | isAsc = false;
42 | return this;
43 | }
44 |
45 | protected String buildSql() {
46 | StringBuilder sb = select.build();
47 | sb.append(" where ");
48 | sb.append(buildWhere());
49 | return sb.toString();
50 | }
51 |
52 | protected String buildWhere() {
53 | StringBuilder sb = buildCondition();
54 | if (orderBy != null) {
55 | sb.append(" order by ");
56 | sb.append(orderBy.name);
57 | if (!isAsc) {
58 | sb.append(" desc ");
59 | }
60 | }
61 | sb.append(" limit ?,?;");
62 | return sb.toString();
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return getClass().getSimpleName() + "{" + buildSql() + "}";
68 | }
69 |
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/SelectSegment.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.Segment;
5 | import cn.liberg.database.BaseDao;
6 |
7 | import java.sql.ResultSet;
8 | import java.sql.SQLException;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * 查询数据表的一部分列
14 | *
15 | * 泛型,用于标记实体类型
16 | * @author Liberg
17 | */
18 | public class SelectSegment extends Select> {
19 | private Column[] columns;
20 |
21 | public SelectSegment(BaseDao dao, Column... columns) {
22 | super(dao);
23 | this.columns = columns;
24 | }
25 |
26 | @Override
27 | protected void appendColumnsTo(StringBuilder sql) {
28 | for (Column column : columns) {
29 | sql.append(column.name);
30 | sql.append(',');
31 | }
32 | sql.deleteCharAt(sql.length()-1);
33 | }
34 |
35 | @Override
36 | public Segment readOne(ResultSet resultSet) throws SQLException {
37 | if(resultSet.next()) {
38 | Segment segment = new Segment(dao);
39 | int index = 1;
40 | for (Column column : columns) {
41 | segment.put(column.shortName, column.getValue(resultSet, index++));
42 | }
43 | return segment;
44 | } else {
45 | return null;
46 | }
47 | }
48 |
49 | @Override
50 | public List> readAll(ResultSet resultSet) throws SQLException {
51 | List> list = new ArrayList<>();
52 | while (resultSet.next()) {
53 | Segment segment = new Segment(dao);
54 | int index = 1;
55 | for (Column column : columns) {
56 | segment.put(column.shortName, column.getValue(resultSet, index++));
57 | }
58 | list.add(segment);
59 | }
60 | return list;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/PreparedSelectSegment.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.Segment;
5 | import cn.liberg.database.BaseDao;
6 |
7 | import java.sql.ResultSet;
8 | import java.sql.SQLException;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * 查询数据表的一部分列
14 | *
15 | * 指代实体类型
16 | * @author Liberg
17 | */
18 | public class PreparedSelectSegment extends PreparedSelect> {
19 | private Column[] columns;
20 |
21 | public PreparedSelectSegment(BaseDao dao, Column... columns) {
22 | super(dao);
23 | this.columns = columns;
24 | }
25 |
26 | @Override
27 | protected void appendColumns(StringBuilder sb) {
28 | for (Column column : columns) {
29 | sb.append(column.name);
30 | sb.append(',');
31 | }
32 | sb.deleteCharAt(sb.length()-1);
33 | }
34 |
35 | @Override
36 | public Segment readOne(ResultSet resultSet) throws SQLException {
37 | if(resultSet.next()) {
38 | Segment segment = new Segment(dao);
39 | int index = 1;
40 | for (Column column : columns) {
41 | segment.put(column.shortName, column.getValue(resultSet, index++));
42 | }
43 | return segment;
44 | } else {
45 | return null;
46 | }
47 | }
48 |
49 | @Override
50 | public List> readAll(ResultSet resultSet) throws SQLException {
51 | List> list = new ArrayList<>();
52 | while (resultSet.next()) {
53 | Segment segment = new Segment(dao);
54 | int index = 1;
55 | for (Column column : columns) {
56 | segment.put(column.shortName, column.getValue(resultSet, index++));
57 | }
58 | list.add(segment);
59 | }
60 | return list;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/CachedColumnPair.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import cn.liberg.cache.ConcurrentLRUCache;
4 | import cn.liberg.database.Condition;
5 | import cn.liberg.database.WhereMeta;
6 |
7 | public class CachedColumnPair implements ICacheEntity {
8 | private CachedColumn column1;
9 | private CachedColumn column2;
10 | private final ConcurrentLRUCache lruCache;
11 |
12 | public CachedColumnPair(CachedColumn column1, CachedColumn column2, int capacity) {
13 | this.column1 = column1;
14 | this.column2 = column2;
15 | lruCache = new ConcurrentLRUCache(capacity);;
16 | }
17 |
18 | public E getFromCache(F1 key1, F2 key2) {
19 | return lruCache.get(key1 + "_" + key2);
20 | }
21 |
22 | public void setToCache(F1 value1, F2 value2, E entity) {
23 | lruCache.put(value1 + "_" + value2, entity);
24 | column1.setToCache(value1, entity);
25 | column2.setToCache(value2, entity);
26 | }
27 |
28 | @Override
29 | public void put(E entity) {
30 | setToCache(column1.get(entity), column2.get(entity), entity);
31 | }
32 |
33 | @Override
34 | public void remove(E entity) {
35 | lruCache.remove(column1.get(entity) + "_" + column2.get(entity));
36 | }
37 |
38 | @Override
39 | public void clear() {
40 | lruCache.clear();
41 | }
42 |
43 | public String build(F1 value1, F2 value2) {
44 | StringBuilder sb = new StringBuilder();
45 | sb.append(column1.name);
46 | sb.append(Condition.EQ);
47 | sb.append(value1);
48 | sb.append(WhereMeta.AND);
49 | sb.append(column2.name);
50 | sb.append(Condition.EQ);
51 | sb.append(value2);
52 | return sb.toString();
53 | }
54 |
55 | public int size() {
56 | return lruCache.size();
57 | }
58 |
59 | public int capacity() {
60 | return lruCache.capacity();
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/database/TableBuilderTest.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.Field;
5 | import cn.liberg.core.IntegerColumn;
6 | import cn.liberg.core.StringColumn;
7 | import cn.liberg.support.data.DBConfig;
8 | import cn.liberg.support.data.DBImpl;
9 | import org.junit.Test;
10 |
11 | import java.sql.ResultSet;
12 | import java.sql.SQLException;
13 |
14 | import static org.junit.Assert.assertEquals;
15 |
16 | public class TableBuilderTest {
17 |
18 | @Test
19 | public void testBuild() {
20 | DBImpl dbImpl = new DBImpl(new DBConfig());
21 |
22 | Field columnName = new Field("name", "n"){
23 | @Override
24 | public String getValue(ResultSet rs, int columnIndex) throws SQLException {
25 | return null;
26 | }
27 | };
28 | Field columnPassword = new Field("password", "n"){
29 | @Override
30 | public String getValue(ResultSet rs, int columnIndex) throws SQLException {
31 | return null;
32 | }
33 | };
34 | Field columnAge = new Field("age", "a"){
35 | @Override
36 | public Integer getValue(ResultSet rs, int columnIndex) throws SQLException {
37 | return null;
38 | }
39 | };
40 |
41 | TableBuilder tb = new TableBuilder("user");
42 | tb.add(columnName, true, dbImpl.typeString(63), "名称");
43 | tb.add(columnPassword, dbImpl.typeString(63));
44 | tb.add(columnAge, dbImpl.typeInt(), "年龄");
45 |
46 |
47 | String sql = tb.build();
48 | String expectedSql = "CREATE TABLE IF NOT EXISTS user(id BIGINT PRIMARY KEY AUTO_INCREMENT" +
49 | ",_name VARCHAR(63) NULL COMMENT '名称'," +
50 | "_password VARCHAR(63) NULL" +
51 | ",_age INT NOT NULL DEFAULT 0 COMMENT '年龄'" +
52 | ",KEY `user__name`(_name));";
53 | assertEquals(sql, expectedSql);
54 | }
55 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | cn.liberg
8 | liberg
9 | 2.0.0
10 | Liberg
11 | A high-performance and lightweight web basis for SpringBoot.
12 |
13 |
14 | 1.8
15 | 1.8
16 | 1.8
17 |
18 |
19 |
20 |
21 | org.slf4j
22 | slf4j-api
23 | 1.7.30
24 |
25 |
26 |
27 | ch.qos.logback
28 | logback-classic
29 | 1.2.3
30 | test
31 |
32 |
33 |
34 | mysql
35 | mysql-connector-java
36 | 8.0.28
37 |
38 |
39 |
40 | junit
41 | junit
42 | 4.13.1
43 | test
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | org.apache.maven.plugins
52 | maven-compiler-plugin
53 | 3.7.0
54 |
55 | 1.8
56 | 1.8
57 | UTF-8
58 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/StatusCode.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * 该类定义内部状态响应码(1000以内)
8 | *
9 | * 业务系统在定义自己的错误码时,应该至少从1001开始。
10 | */
11 | public enum StatusCode implements IStatusCode {
12 | OK(200, ""),
13 | ERROR_UNKNOW(0,"Unknown error"),
14 | ERROR_SERVER(501, "服务端出错"),
15 | ERROR_DB(502, "数据库出错"),
16 | ERROR_NET(503, "网络错误"),
17 | ERROR_JSON(504, "JSON解析错误"),
18 | OPERATION_ILLEGAL(505, "非法操作"),
19 | OPERATION_FAILED(506, "操作失败"),
20 | PARAMS_INVALID(507, "参数错误");
21 |
22 |
23 | public static IStatusCode def(int code, String desc) {
24 | return new IStatusCode() {
25 | @Override
26 | public int code() {
27 | return code;
28 | }
29 | @Override
30 | public String desc() {
31 | return desc;
32 | }
33 | };
34 | }
35 |
36 | /**
37 | * 发生了某些情况,临时给客户端一个desc的内容提示。
38 | * 采用特殊响应状态码999
39 | */
40 | public static IStatusCode def(String desc) {
41 | return def(999, desc);
42 | }
43 |
44 | private int code;
45 | private String desc;
46 | StatusCode(int code, String desc) {
47 | this.code = code;
48 | this.desc = desc;
49 | }
50 |
51 |
52 | @Override
53 | public int code() {
54 | return code;
55 | }
56 |
57 | @Override
58 | public String desc() {
59 | return desc;
60 | }
61 |
62 | public void reset(int code, String desc) {
63 | Logger logger = LoggerFactory.getLogger(StatusCode.class);
64 | if (logger.isDebugEnabled()) {
65 | logger.debug("reset: {}({})", this.code, this.desc);
66 | logger.debug(" to: {}({})", code, desc);
67 | }
68 | this.code = code;
69 | this.desc = desc;
70 | }
71 |
72 | public void reset(String desc) {
73 | Logger logger = LoggerFactory.getLogger(StatusCode.class);
74 | if (logger.isDebugEnabled()) {
75 | logger.debug("reset: {}({})", code, this.desc);
76 | logger.debug(" to: {}({})", code, desc);
77 | }
78 | this.desc = desc;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/TableBuilder.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 |
4 | import cn.liberg.core.Column;
5 | import cn.liberg.core.Field;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | /**
11 | * 为了保持{@code XxxDao}更轻量级,实体中的字段除了名称外,其他信息并没有记录到相应的{@link Column}中,
12 | * 而是在构造建表语句时,由{@code TableBuilder.add(...)}方法中进行传入。
13 | * 传入的信息包括: 数据列的类型、长度、是否是索引字段、列注释信息。
14 | *
15 | * @author Liberg
16 | *
17 | * @see Column
18 | */
19 | public class TableBuilder {
20 | private String tableName;
21 | private StringBuilder sb;
22 | private List listIndex;
23 |
24 | public TableBuilder(String tableName) {
25 | this.tableName = tableName;
26 | sb = new StringBuilder(1024);
27 | sb.append("CREATE TABLE IF NOT EXISTS ");
28 | sb.append(tableName);
29 | sb.append("(id BIGINT PRIMARY KEY AUTO_INCREMENT,");
30 | listIndex = new ArrayList<>();
31 | }
32 |
33 | public void add(Field column, String type) {
34 | add(column, false, type, null);
35 | }
36 |
37 | public void add(Field column, String type, String comment) {
38 | add(column, false, type, comment);
39 | }
40 |
41 | public void add(Field column, boolean isIndex, String type) {
42 | add(column, isIndex, type, null);
43 | }
44 |
45 | public void add(Field column, boolean isIndex, String type, String comment) {
46 | sb.append(column.name);
47 | sb.append(" ");
48 | sb.append(type);
49 | if(comment!=null) {
50 | sb.append(" COMMENT '");
51 | sb.append(comment);
52 | sb.append('\'');
53 | }
54 | sb.append(',');
55 | if(isIndex) {
56 | listIndex.add(column);
57 | }
58 | }
59 |
60 | public String build() {
61 | for(Field column : listIndex) {
62 | sb.append("KEY `");
63 | sb.append(tableName);
64 | sb.append('_');
65 | sb.append(column.name);
66 | sb.append("`(");
67 | sb.append(column.name);
68 | sb.append("),");
69 | }
70 | sb.deleteCharAt(sb.length()-1);
71 | sb.append(");");
72 | return sb.toString();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/IDataBase.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import java.sql.SQLException;
4 | import java.sql.Statement;
5 |
6 | /**
7 | * @author Liberg
8 | */
9 | public interface IDataBase {
10 |
11 | /**
12 | * 获取数据库的名称
13 | * @return
14 | */
15 | public String getName();
16 |
17 | /**
18 | * 获取数据库的当前版本
19 | * @return
20 | */
21 | public int getCurrentVersion();
22 |
23 | /**
24 | * 获取数据库的配置信息
25 | * @return
26 | */
27 | public IDataBaseConf getConfig();
28 |
29 | /**
30 | * 此方法中完成数据库表结构的创建
31 | * @param stat
32 | * @throws SQLException
33 | */
34 | public void createTable(Statement stat) throws SQLException;
35 |
36 | /**
37 | * 实现数据库从dbVersion到newVersion的版本升级逻辑,
38 | * 升级内容包括表结构升级和数据增删改
39 | * @param stat
40 | * @param dbVersion
41 | * @param newVersion
42 | * @return
43 | * @throws SQLException
44 | */
45 | public int upgrade(Statement stat, int dbVersion, int newVersion) throws SQLException;
46 |
47 | /**
48 | * 此方法中完成数据库数据的初始化,比如创建一个超级管理员等
49 | */
50 | public void initData();
51 |
52 | default String typeByte() {
53 | return "TINYINT NOT NULL DEFAULT 0";
54 | }
55 |
56 | default String typeByte(int defVal) {
57 | return "TINYINT NOT NULL DEFAULT " + defVal;
58 | }
59 |
60 | default String typeInt() {
61 | return "INT NOT NULL DEFAULT 0";
62 | }
63 |
64 | default String typeInt(int defVal) {
65 | return "INT NOT NULL DEFAULT " + defVal;
66 | }
67 |
68 | default String typeLong() {
69 | return "BIGINT NOT NULL DEFAULT 0";
70 | }
71 |
72 | default String typeLong(long defVal) {
73 | return "BIGINT NOT NULL DEFAULT " + defVal;
74 | }
75 |
76 | default String typeString(int len) {
77 | return "VARCHAR(" + len + ") NULL";
78 | }
79 |
80 | default String typeString() {
81 | return "VARCHAR(255) NULL";
82 | }
83 |
84 | default String typeString(int len, String defVal) {
85 | return "VARCHAR(" + len + ") NOT NULL DEFAULT '" + defVal + "'";
86 | }
87 |
88 | default String typeString(String defVal) {
89 | return "VARCHAR(255) NOT NULL DEFAULT '" + defVal + "'";
90 | }
91 |
92 | default String typeText() {
93 | return "TEXT DEFAULT NULL";
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/CachedColumnTrio.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import cn.liberg.cache.ConcurrentLRUCache;
4 | import cn.liberg.database.Condition;
5 | import cn.liberg.database.WhereMeta;
6 |
7 | public class CachedColumnTrio implements ICacheEntity {
8 | private CachedColumn column1;
9 | private CachedColumn column2;
10 | private CachedColumn column3;
11 | private final ConcurrentLRUCache lruCache;
12 |
13 | public CachedColumnTrio(CachedColumn column1,
14 | CachedColumn column2,
15 | CachedColumn column3,
16 | int capacity) {
17 | this.column1 = column1;
18 | this.column2 = column2;
19 | this.column3 = column3;
20 | lruCache = new ConcurrentLRUCache(capacity);;
21 | }
22 |
23 | public E getFromCache(F1 key1, F2 key2, F3 key3) {
24 | return lruCache.get(key1 + "_" + key2 + "_" + key3);
25 | }
26 |
27 | public void setToCache(F1 value1, F2 value2, F3 value3, E entity) {
28 | lruCache.put(value1 + "_" + value2 + "_" + value3, entity);
29 | column1.setToCache(value1, entity);
30 | column2.setToCache(value2, entity);
31 | column3.setToCache(value3, entity);
32 | }
33 |
34 | @Override
35 | public void put(E entity) {
36 | setToCache(column1.get(entity), column2.get(entity), column3.get(entity), entity);
37 | }
38 |
39 | @Override
40 | public void remove(E entity) {
41 | lruCache.remove(column1.get(entity) + "_" + column2.get(entity) + "_" + column3.get(entity));
42 | }
43 |
44 | @Override
45 | public void clear() {
46 | lruCache.clear();
47 | }
48 |
49 | public String build(F1 value1, F2 value2, F3 value3) {
50 | StringBuilder sb = new StringBuilder();
51 | sb.append(column1.name);
52 | sb.append(Condition.EQ);
53 | sb.append(value1);
54 | sb.append(WhereMeta.AND);
55 | sb.append(column2.name);
56 | sb.append(Condition.EQ);
57 | sb.append(value2);
58 | sb.append(WhereMeta.AND);
59 | sb.append(column3.name);
60 | sb.append(Condition.EQ);
61 | sb.append(value3);
62 | return sb.toString();
63 | }
64 |
65 | public int size() {
66 | return lruCache.size();
67 | }
68 |
69 | public int capacity() {
70 | return lruCache.capacity();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/Field.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import java.sql.ResultSet;
4 | import java.sql.SQLException;
5 |
6 | /**
7 | * 数据表的列抽象
8 | * 将实体类的成员字段名称映射为数据表的列名
9 | *
10 | * @param 列的类型
11 | */
12 | public abstract class Field {
13 | public static final String ID = "id";
14 | /**
15 | * 数据表列名称
16 | */
17 | public final String name;
18 | /**
19 | * 实体字段名称
20 | */
21 | public final String entityFieldName;
22 | /**
23 | * 实体字段名称的简写,同一实体内部不重复
24 | * 便于跟FastJSON等框架配合,使返回客户端的json数据变得简短
25 | */
26 | public final String shortName;
27 |
28 | /**
29 | * @param entityFieldName 实体字段名称
30 | * @param shortName 实体字段的简称,取各个单词首字母,全小写。
31 | * 若可能重复,则通过1/2/3...进行编号
32 | */
33 | public Field(String entityFieldName, String shortName) {
34 | this.entityFieldName = entityFieldName;
35 | this.name = parseColumnName(entityFieldName);
36 | this.shortName = shortName;
37 | }
38 |
39 | /**
40 | * 从数据库查询结果的ResultSet中读取列的值
41 | *
42 | * @param rs
43 | * @param columnIndex
44 | * @return
45 | * @throws SQLException
46 | */
47 | public abstract F getValue(ResultSet rs, int columnIndex) throws SQLException;
48 |
49 | @Override
50 | public String toString() {
51 | return name;
52 | }
53 |
54 | /**
55 | * 将实体字段名称,映射为数据表的列名称
56 | * eg:
57 | * userName --> _user_name
58 | * mPackage --> _package
59 | *
60 | * id --> id,是唯一例外
61 | *
62 | * @param entityFieldName
63 | * @return
64 | */
65 | public static String parseColumnName(String entityFieldName) {
66 | if (ID.equals(entityFieldName)) {
67 | return ID;
68 | }
69 | boolean first = true;
70 | int start = 0;
71 | if (entityFieldName.length() > 1 && entityFieldName.charAt(0) == 'm'
72 | && Character.isUpperCase(entityFieldName.charAt(1))) {
73 | first = false;
74 | start = 1;
75 | }
76 |
77 | StringBuilder sb = new StringBuilder("_");
78 | for (int i = start; i < entityFieldName.length(); i++) {
79 | char c = entityFieldName.charAt(i);
80 | if (c >= 'A' && c <= 'Z') {
81 | if (first) {
82 | sb.append('_');
83 | first = false;
84 | }
85 | sb.append((char) (c + 32));
86 | } else {
87 | first = true;
88 | sb.append(c);
89 | }
90 | }
91 | return sb.toString();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/DBImpl.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data;
2 |
3 | import cn.liberg.database.IDataBase;
4 | import cn.liberg.database.IDataBaseConf;
5 | import cn.liberg.database.TableBuilder;
6 | import cn.liberg.support.data.dao.RoleDao;
7 | import cn.liberg.support.data.dao.UserDao;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import java.sql.SQLException;
12 | import java.sql.Statement;
13 |
14 | public class DBImpl implements IDataBase {
15 | private static final Logger logger = LoggerFactory.getLogger(DBImpl.class);
16 |
17 | protected final IDataBaseConf dbConf;
18 | protected final int dbVersion = 1;
19 | protected final String dbName;
20 |
21 | public DBImpl(IDataBaseConf dbConf) {
22 | this.dbConf = dbConf;
23 | dbName = dbConf.getDbName();
24 | }
25 |
26 | @Override
27 | public int upgrade(Statement stat, int dbVersion, int newVersion) throws SQLException {
28 | DBUpgrader dbUpgrader = new DBUpgrader(dbConf);
29 | return dbUpgrader.upgrade(stat, dbVersion, newVersion);
30 | }
31 |
32 | @Override
33 | public void initData() {
34 | try {
35 | DBInitializer initializer = new DBInitializer();
36 | initializer.initData();
37 | } catch (Exception e) {
38 | logger.error("initData failed...", e);
39 | }
40 | }
41 |
42 | @Override
43 | public String getName() {
44 | return dbName;
45 | }
46 |
47 | @Override
48 | public int getCurrentVersion() {
49 | return dbVersion;
50 | }
51 |
52 | @Override
53 | public IDataBaseConf getConfig() {
54 | return dbConf;
55 | }
56 |
57 | @Override
58 | public void createTable(Statement stat) throws SQLException {
59 | createTableRole(stat);
60 | createTableUser(stat);
61 | }
62 |
63 | protected void createTableRole(Statement stat) throws SQLException {
64 | TableBuilder tb = new TableBuilder(RoleDao.TABLE_NAME);
65 | tb.add(RoleDao.columnName, typeString(31));
66 | tb.add(RoleDao.columnPermissions, typeText());
67 | stat.executeUpdate(tb.build());
68 | }
69 |
70 | protected void createTableUser(Statement stat) throws SQLException {
71 | UserDao dao = UserDao.self();
72 | TableBuilder tb = new TableBuilder(UserDao.TABLE_NAME);
73 | tb.add(dao.columnName, true, typeString());
74 | tb.add(dao.columnPassword, typeString(), "这是\"\"空字符串");
75 | tb.add(dao.columnAge, typeByte());
76 | tb.add(dao.columnRoleId, typeLong());
77 | tb.add(dao.columnCreateTime, typeLong());
78 | stat.executeUpdate(tb.build());
79 | }
80 |
81 | }
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/TableAlteration.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import com.mysql.cj.exceptions.MysqlErrorNumbers;
4 | import org.slf4j.Logger;
5 | import org.slf4j.LoggerFactory;
6 |
7 | import java.sql.SQLException;
8 | import java.sql.Statement;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * 数据表结构升级实现类。
14 | *
15 | *
出于数据安全性的考虑,Liberg鼓励复用和新增列,不建议删除已有的列。
16 | * 因此TableAlteration类没有提供"deleteColumn"之类的的方法
17 | *
18 | * @author Liberg
19 | */
20 | public class TableAlteration {
21 | private static final Logger logger = LoggerFactory.getLogger(TableAlteration.class);
22 |
23 | private String tableName;
24 | private List sqls;
25 | private String alterPrefix;
26 |
27 | public TableAlteration(String tableName) {
28 | this.tableName = tableName;
29 | sqls = new ArrayList<>();
30 | alterPrefix = "ALTER TABLE " + tableName;
31 | }
32 |
33 | public String getTableName() {
34 | return tableName;
35 | }
36 |
37 | public void exec(Statement stat) throws SQLException {
38 | try {
39 | for (String sql : sqls) {
40 | stat.execute(sql);
41 | }
42 | } catch (SQLException e) {
43 | int errorCode = e.getErrorCode();
44 | if (errorCode == MysqlErrorNumbers.ER_DUP_FIELDNAME
45 | || errorCode == MysqlErrorNumbers.ER_DUP_KEYNAME
46 | || errorCode == MysqlErrorNumbers.ER_CANT_DROP_FIELD_OR_KEY ) {
47 | // 1060 Duplicate column name
48 | // 1061 Duplicate key name
49 | // 1091 Can't DROP 'xxx'. Check that column/key exists
50 | logger.warn("TableAlteration exception: {}, {}", errorCode, e.getMessage());
51 | } else {
52 | throw e;
53 | }
54 | }
55 | }
56 |
57 | public TableAlteration addIndex(String column) {
58 | sqls.add(alterPrefix + " ADD INDEX(`" + column + "`);");
59 | return this;
60 | }
61 |
62 | public TableAlteration dropIndex(String column) {
63 | sqls.add(alterPrefix + " DROP INDEX(`" + column + "`);");
64 | return this;
65 | }
66 |
67 | public TableAlteration addColumn(String column, String type, String afterOf) {
68 | String fmt = " ADD COLUMN `%1$s` %2$s AFTER `%3$s`;";
69 | sqls.add(alterPrefix + String.format(fmt, column, type, afterOf));
70 | return this;
71 | }
72 |
73 | public TableAlteration modifyColumn(String column, String newType) {
74 | String fmt = " MODIFY %1$s %2$s;";
75 | sqls.add(alterPrefix + String.format(fmt, column, newType));
76 | return this;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/dao/impl/RoleDaoImpl.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data.dao.impl;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.IdColumn;
5 | import cn.liberg.core.StringColumn;
6 | import cn.liberg.database.BaseDao;
7 | import cn.liberg.support.data.entity.Role;
8 |
9 | import java.sql.PreparedStatement;
10 | import java.sql.ResultSet;
11 | import java.sql.SQLException;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class RoleDaoImpl extends BaseDao {
16 | public static final String TABLE_NAME = "role";
17 |
18 | public static final IdColumn columnId = new IdColumn() {
19 | @Override
20 | public Long get(Role entity) {
21 | return entity.id;
22 | }
23 |
24 | @Override
25 | public void set(Role entity, Long value) {
26 | entity.id = value;
27 | }
28 | };
29 |
30 | public static final Column columnName = new StringColumn("name", "n"){
31 |
32 | @Override
33 | public String get(Role entity) {
34 | return entity.name;
35 | }
36 |
37 | @Override
38 | public void set(Role entity, String value) {
39 | entity.name = value;
40 | }
41 | };
42 | public static final Column columnPermissions = new StringColumn("permissions", "p") {
43 |
44 | @Override
45 | public String get(Role entity) {
46 | return entity.permissions;
47 | }
48 |
49 | @Override
50 | public void set(Role entity, String value) {
51 | entity.permissions = value;
52 | }
53 | };
54 |
55 | protected RoleDaoImpl() {
56 | super(TABLE_NAME, null);
57 | }
58 |
59 | @Override
60 | public Class getEntityClazz() {
61 | return Role.class;
62 | }
63 |
64 | @Override
65 | public Role buildEntity(ResultSet rs) throws SQLException {
66 | Role entity = new Role();
67 | entity.id = rs.getLong(1);
68 | entity.name = rs.getString(2);
69 | entity.permissions = rs.getString(3);
70 | return entity;
71 | }
72 |
73 | @Override
74 | protected void fillPreparedStatement(Role entity, PreparedStatement ps) throws SQLException {
75 | ps.setString(1, entity.name);
76 | ps.setString(2, entity.permissions);
77 | }
78 |
79 | @Override
80 | public Column getIdColumn() {
81 | return columnId;
82 | }
83 |
84 | @Override
85 | public List initColumns() {
86 | List columns = new ArrayList<>(4);
87 | columns.add(columnName);
88 | columns.add(columnPermissions);
89 | return columns;
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/Condition.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | /**
4 | * 用{@code Condition}定义单个表达比较语义的子条件。
5 | *
6 | * {@code Condition}和{@link Joints}一起组合出完整的查询where条件。
7 | *
8 | * @author Liberg
9 | * @see Joints
10 | */
11 | public class Condition extends WhereMeta {
12 | public static final String EQ = "=";
13 | public static final String NE = "<>";
14 | public static final String GE = ">=";
15 | public static final String GT = ">";
16 | public static final String LE = "<=";
17 | public static final String LT = "<";
18 | public static final String LIKE = " like ";
19 |
20 | public Condition(String name, String link, String value) {
21 | super(name + link + value);
22 | }
23 |
24 | @Override
25 | public boolean isCondition() {
26 | return true;
27 | }
28 |
29 | public static String eq(String column, String value) {
30 | return column + EQ + SqlDefender.format(value);
31 | }
32 |
33 | public static String eq(String column, Number value) {
34 | return column + EQ + value;
35 | }
36 |
37 | public static String eq(String column1, Number value1, String column2, Number value2) {
38 | return eq(column1, value1) + AND + eq(column2, value2);
39 | }
40 |
41 | public static String eq(String column1, String value1, String column2, String value2) {
42 | return eq(column1, value1) + AND + eq(column2, value2);
43 | }
44 |
45 | public static String eq(String column1, String value1, String column2, Number value2) {
46 | return eq(column1, value1) + AND + eq(column2, value2);
47 | }
48 |
49 | public static String eq(String column1, Number value1, String column2, String value2) {
50 | return eq(column1, value1) + AND + eq(column2, value2);
51 | }
52 |
53 | public static String ne(String column, String value) {
54 | return column + NE + SqlDefender.format(value);
55 | }
56 |
57 | public static String ne(String column, Number value) {
58 | return column + NE + value;
59 | }
60 |
61 | public static String gt(String column, Number value) {
62 | return column + GT + value;
63 | }
64 |
65 | public static String lt(String column, Number value) {
66 | return column + LT + value;
67 | }
68 |
69 | public static String ge(String column, Number value) {
70 | return column + GE + value;
71 | }
72 |
73 | public static String le(String column, Number value) {
74 | return column + LE + value;
75 | }
76 |
77 | public static String range(String column, Number begin, Number end) {
78 | return column + GE + begin + AND + column + LE + end;
79 | }
80 |
81 | public static final String like(String column, String value) {
82 | return column + LIKE + SqlDefender.format(value);
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/core/Response.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.core;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * Response定义通用的、返回给客户端的响应信息
7 | *
8 | * @author Liberg
9 | */
10 | public class Response {
11 | /**
12 | * 响应状态码
13 | */
14 | private int code = StatusCode.OK.code();
15 | /**
16 | * 状态码描述
17 | */
18 | private String message = "";
19 | /**
20 | * 响应数据
21 | */
22 | private List datas;
23 | /**
24 | * 响应附加信息
25 | */
26 | public Map metas;
27 |
28 | public static Response ok() {
29 | return new Response();
30 | }
31 |
32 | public static Response ok(List datas) {
33 | return new Response().setDatas(datas);
34 | }
35 |
36 | public static Response ok(Object data) {
37 | return new Response().putData(data);
38 | }
39 |
40 | public static Response fail(OperatorException e) {
41 | Response res = Response.of(e.statusCode());
42 | StackTraceElement[] stackTrace = e.getStackTrace();
43 | if(stackTrace.length > 0) {
44 | res.putMeta(e.getClass().getName(), stackTrace[0].toString());
45 | }
46 | return res;
47 | }
48 |
49 | public int getCode() {
50 | return code;
51 | }
52 |
53 | public String getMessage() {
54 | return message;
55 | }
56 |
57 | public List getDatas() {
58 | return datas;
59 | }
60 |
61 | public Map getMetas() {
62 | return metas;
63 | }
64 |
65 | public Response setDatas(List datas) {
66 | this.datas = datas;
67 | return this;
68 | }
69 |
70 | public Response putData(Object data) {
71 | if (datas == null) {
72 | datas = new ArrayList<>();
73 | }
74 | if(data != null) {
75 | datas.add(data);
76 | }
77 | return this;
78 | }
79 |
80 | public Response setMetas(Map metas) {
81 | this.metas = metas;
82 | return this;
83 | }
84 |
85 | public Response putMeta(String key, Object value) {
86 | if (metas == null) {
87 | metas = new HashMap(16);
88 | }
89 | if(key != null) {
90 | metas.put(key, value);
91 | }
92 | return this;
93 | }
94 |
95 | public static Response of(IStatusCode sc) {
96 | Response res = new Response();
97 | res.code = sc.code();
98 | res.message = sc.desc();
99 | return res;
100 | }
101 |
102 | public static Response of(int code, String message) {
103 | Response res = new Response();
104 | res.code = code;
105 | res.message = message;
106 | return res;
107 | }
108 |
109 | @Override
110 | public String toString() {
111 | return "Response{" +
112 | "code=" + code +
113 | ", message='" + message + '\'' +
114 | ", datas=" + datas +
115 | ", metas=" + metas +
116 | '}';
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/DBVersion.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.sql.ResultSet;
7 | import java.sql.SQLException;
8 | import java.sql.Statement;
9 | import java.util.HashMap;
10 | import java.util.Map;
11 |
12 | /**
13 | * 负责创建{@code db_version}表,
14 | * 维护和查询当前数据库的版本。
15 | *
16 | * @author Liberg
17 | * @see DBVersionManager
18 | */
19 | public final class DBVersion {
20 | private static final Logger logger = LoggerFactory.getLogger(DBVersion.class);
21 | private static final String TABLE_NAME = "db_version";
22 | private static final String COLUMN_DB = "_db";
23 | private static final String COLUMN_CODE = "_version_code";
24 | private static Map map = null;
25 |
26 | private Statement stat;
27 |
28 | public DBVersion(Statement stat) {
29 | this.stat = stat;
30 | if (map == null) {
31 | initMap(stat);
32 | }
33 | }
34 |
35 | private static synchronized void initMap(Statement stat) {
36 | if (map != null) {
37 | return;
38 | }
39 | map = new HashMap<>(16);
40 | String sql = String.format("select %1$s,%2$s from %3$s", COLUMN_DB, COLUMN_CODE, TABLE_NAME);
41 | ResultSet rs = null;
42 | try {
43 | createTableIfAbsent(stat);
44 | rs = stat.executeQuery(sql);
45 | String dbName;
46 | int code;
47 | while (rs.next()) {
48 | dbName = rs.getString(COLUMN_DB);
49 | code = rs.getInt(COLUMN_CODE);
50 | map.put(dbName, code);
51 | }
52 | } catch (Exception e) {
53 | logger.error("DBVersion init failed.", e);
54 | } finally {
55 | if (rs != null) {
56 | try {
57 | rs.close();
58 | } catch (SQLException e) {
59 | }
60 | }
61 | }
62 | }
63 |
64 | private static void createTableIfAbsent(Statement stat) throws SQLException {
65 | String sql = "create table if Not exists " + TABLE_NAME
66 | + " (`id` BIGINT primary key AUTO_INCREMENT,"
67 | + COLUMN_DB + " VARCHAR(255),"
68 | + COLUMN_CODE + " BIGINT"
69 | + ");";
70 | stat.executeUpdate(sql);
71 | }
72 |
73 | public void saveVersion(String dbName, int version, boolean isUpdate) throws SQLException {
74 | String sql;
75 | if (isUpdate) {
76 | sql = " update %1$s set %2$s=%3$s where %4$s='%5$s' ; ";
77 | sql = String.format(sql, TABLE_NAME, COLUMN_CODE, version, COLUMN_DB, dbName);
78 | } else {
79 | sql = " insert %1$s(%2$s,%3$s) values('%4$s',%5$s) ; ";
80 | sql = String.format(sql, TABLE_NAME, COLUMN_DB, COLUMN_CODE, dbName, version);
81 | }
82 | stat.executeUpdate(sql);
83 | map.put(dbName, version);
84 | }
85 |
86 | public static int getVersion(String dbName) {
87 | Integer code = map.get(dbName);
88 | return code == null ? -1 : code.intValue();
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/Where.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Field;
4 | import cn.liberg.database.Condition;
5 | import cn.liberg.database.Joints;
6 | import cn.liberg.database.SqlDefender;
7 | import cn.liberg.database.WhereMeta;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * where条件查询
14 | *
15 | * @author Liberg
16 | */
17 | public class Where {
18 | protected List metaList = new ArrayList<>();
19 |
20 | protected void appendConditionTo(StringBuilder sql) {
21 | if(metaList.size() > 0) {
22 | for (WhereMeta e : metaList) {
23 | sql.append(e.value);
24 | sql.append(" ");
25 | }
26 | } else {
27 | sql.append("1=1 ");
28 | }
29 | }
30 |
31 | /**
32 | * equals or not
33 | */
34 | public T eq(Field column, String value) {
35 | return _add(column, Condition.EQ, SqlDefender.format(value));
36 | }
37 |
38 | public T eq(Field extends Number> column, Number value) {
39 | return _add(column, Condition.EQ, "" + value);
40 | }
41 |
42 | public T ne(Field column, String value) {
43 | return _add(column, Condition.NE, SqlDefender.format(value));
44 | }
45 |
46 | public T ne(Field extends Number> column, Number value) {
47 | return _add(column, Condition.NE, "" + value);
48 | }
49 |
50 | /**
51 | * like
52 | */
53 | public T like(Field column, String value) {
54 | return _add(column, Condition.LIKE, SqlDefender.format(value));
55 | }
56 |
57 | /**
58 | * great equal or great than
59 | */
60 | public T ge(Field extends Number> column, Number value) {
61 | return _add(column, Condition.GE, "" + value);
62 | }
63 |
64 | public T gt(Field extends Number> column, Number value) {
65 | return _add(column, Condition.GT, "" + value);
66 | }
67 |
68 | /**
69 | * less equal or less than
70 | */
71 | public T le(Field extends Number> column, Number value) {
72 | return _add(column, Condition.LE, "" + value);
73 | }
74 |
75 | public T lt(Field extends Number> column, Number value) {
76 | return _add(column, Condition.LT, "" + value);
77 | }
78 |
79 | /**
80 | * and/or/not
81 | */
82 | public T and() {
83 | metaList.add(Joints.AND);
84 | return (T) this;
85 | }
86 |
87 | public T or() {
88 | metaList.add(Joints.OR);
89 | return (T) this;
90 | }
91 |
92 | public T not() {
93 | metaList.add(Joints.NOT);
94 | return (T) this;
95 | }
96 |
97 | public T bracketStart() {
98 | metaList.add(Joints.BRACKET_START);
99 | return (T) this;
100 | }
101 |
102 | public T bracketEnd() {
103 | metaList.add(Joints.BRACKET_END);
104 | return (T) this;
105 | }
106 |
107 | protected T _add(Field column, String link, String value) {
108 | // 两个Condition之间未指定逻辑运算符时,默认用and
109 | if (metaList.size() > 0 && metaList.get(metaList.size() - 1).isCondition()) {
110 | metaList.add(Joints.AND);
111 | }
112 | metaList.add(new Condition(column.name, link, value));
113 | return (T) this;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/DBVersionManager.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.sql.Connection;
7 | import java.sql.SQLException;
8 | import java.sql.Statement;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * 完成数据库的创建、版本升级、数据初始化;维护数据库的版本。
14 | *
15 | * 框架自动维护一张名为"db_version"的表,
16 | * 记录各个数据库的版本
17 | * 不过,常见情况是一个Web项目只对应一个数据库
18 | *
19 | * @author Liberg
20 | */
21 | public class DBVersionManager {
22 | private static final Logger logger = LoggerFactory.getLogger(DBVersionManager.class);
23 | private ArrayList dbCreatorList;
24 | private DBConnector dbConnector;
25 |
26 |
27 | public DBVersionManager(DBConnector dbConnector) {
28 | this.dbConnector = dbConnector;
29 | dbCreatorList = new ArrayList<>();
30 | }
31 |
32 | public void addDatabase(IDataBase creator) {
33 | dbCreatorList.add(creator);
34 | }
35 |
36 | public void dbInit() {
37 | Connection conn = null;
38 | Statement stat = null;
39 | List initDataList = new ArrayList<>();
40 | try {
41 | conn = dbConnector.getConnect();
42 | conn.setAutoCommit(false);
43 | stat = conn.createStatement();
44 | DBVersion dbVer = new DBVersion(stat);
45 | for (IDataBase creator : dbCreatorList) {
46 | int currentVersion = creator.getCurrentVersion();
47 | String dbName = creator.getName();
48 | int oldVersion = DBVersion.getVersion(dbName);
49 | logger.info("connected to DB: {}, dbVer: {}", creator.getName(), oldVersion);
50 | if (oldVersion == -1) {
51 | dbVer.saveVersion(dbName, currentVersion, false);
52 | creator.createTable(stat);
53 | initDataList.add(creator);
54 | } else {
55 | if (oldVersion < currentVersion) {
56 | int version = creator.upgrade(stat, oldVersion, currentVersion);
57 | if (version >= currentVersion) {
58 | dbVer.saveVersion(dbName, version, true);
59 | logger.info("upgrade version, db: {}, from: {} to {}",
60 | creator.getName(), oldVersion, version);
61 | } else {
62 | logger.warn("upgrade error, db: {}, version: {}, expectedVersion: {}, oldVersion: {}",
63 | creator.getName(), version, currentVersion, oldVersion);
64 | }
65 | }
66 | }
67 | }
68 | conn.commit();
69 | } catch (Exception e) {
70 | logger.error("dbInit", e);
71 | } finally {
72 | try {
73 | if (conn != null) {
74 | conn.setAutoCommit(true);
75 | }
76 | if (stat != null) {
77 | stat.close();
78 | }
79 | } catch (SQLException e) {
80 | logger.error("dbInit:finally", e);
81 | }
82 | if (conn != null) {
83 | dbConnector.freeConnection(conn, false);
84 | }
85 | }
86 |
87 | initData(initDataList);
88 | }
89 |
90 | private void initData(List dbs) {
91 | for(IDataBase db : dbs) {
92 | db.initData();
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/database/DBConnectorTest.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 | import cn.liberg.support.data.DBConfig;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 |
7 | import java.sql.*;
8 |
9 | import static org.junit.Assert.assertEquals;
10 |
11 | public class DBConnectorTest {
12 | private static int SIZE = 3;
13 |
14 | private boolean testConnection(Connection conn) throws SQLException {
15 | boolean result;
16 | Statement stat = conn.createStatement();
17 | result = stat.executeQuery("select 1") != null;
18 | stat.close();
19 | return result;
20 | }
21 |
22 | private void repeatTest(DBConnector connector, int curIdx) {
23 | Connection conn = null;
24 | boolean isTxError = false;
25 | try {
26 | conn = connector.getConnect();
27 | // 连接池中拿走一个连接后,预期剩余SIZE-1个
28 | assertEquals(SIZE-1, connector.getFreeCount());
29 |
30 | if (testConnection(conn)) {
31 | System.out.println("OK " + curIdx);
32 | }
33 | } catch (SQLException ex) {
34 | isTxError = DBHelper.isTxError(ex);
35 | System.out.println("Exception: " + ex.getClass().getName());
36 | } finally {
37 | if(isTxError) {
38 | assertEquals(SIZE-1, connector.getFreeCount());
39 | connector.freeAllConnection(conn);
40 | // 如果发生通信错误,释放所有连接后,空闲连接数应为0
41 | assertEquals(0, connector.getFreeCount());
42 | // 发生错误后,连接全部释放,再次调用connector.getConnect()会申请到1个连接
43 | // 为了能让测试继续跑,重置SIZE为1
44 | SIZE = 1;
45 | } else {
46 | connector.freeConnection(conn, false);
47 | // 如果没有发生通信错误,连接归还后还是SIZE个
48 | assertEquals(SIZE, connector.getFreeCount());
49 | }
50 | }
51 | }
52 |
53 | @Test
54 | public void test() {
55 | IDataBaseConf dbConfig = new DBConfig();
56 | DBConnector connector = new DBConnector();
57 | connector.init(dbConfig);
58 |
59 | Connection[] connArray = new Connection[SIZE];
60 | try {
61 | /**
62 | * 先初始化SIZE个连接
63 | */
64 | for (int i = 0; i < connArray.length; i++) {
65 | connArray[i] = connector.getConnect();
66 | }
67 | /**
68 | * SIZE个连接放回到连接池
69 | */
70 | for (int i = 0; i < connArray.length; i++) {
71 | connector.freeConnection(connArray[i], false);
72 | }
73 |
74 | } catch (SQLException e) {
75 | e.printStackTrace();
76 | }
77 | // 预期连接池中有SIZE个空闲连接
78 | assertEquals(SIZE, connector.getFreeCount());
79 |
80 |
81 | int i = 10;
82 | while (i > 0) {
83 | /**
84 | * 多次执行的方法:
85 | * 1、MySQL服务端正常打开后,开始执行几次;
86 | * 2、接着,关掉MySQL服务端,继续执行几次,
87 | * 3、最后,再打开MySQL服务端,继续执行几次。
88 | *
89 | * 测试正确的控制台输出结果应该是:若干个OK + 若干个CommunicationsException + 若干个OK
90 | * OK
91 | * OK
92 | * OK
93 | * Exception: com.mysql.cj.jdbc.exceptions.CommunicationsException
94 | * Exception: com.mysql.cj.jdbc.exceptions.CommunicationsException
95 | * Exception: com.mysql.cj.jdbc.exceptions.CommunicationsException
96 | * OK
97 | * OK
98 | * OK
99 | */
100 | repeatTest(connector, i);
101 |
102 | try {
103 | Thread.sleep(1200);
104 | } catch (InterruptedException e) {
105 | e.printStackTrace();
106 | }
107 | i--;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/session/SessionManager.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.session;
2 |
3 | import cn.liberg.cache.LRUCache;
4 | import cn.liberg.core.PeriodicThread;
5 |
6 | import java.util.Iterator;
7 | import java.util.Map;
8 |
9 | /**
10 | * 会话管理器
11 | *
12 | * @author Liberg
13 | */
14 | public class SessionManager {
15 | private static volatile SessionManager selfInstance = null;
16 | /**
17 | * 默认24小时后session自动过期,需要重新登录
18 | */
19 | private long expiredMillis = 24 * 60 * 60 * 1000L;
20 | /**
21 | * session过期前10分钟内,设置为不再可用,需要重新登录
22 | */
23 | private long reserveMillis = 10 * 60 * 1000L;
24 | private long usableDuration = expiredMillis - reserveMillis;
25 | /**
26 | * 每5分钟清理一次过期的session
27 | */
28 | private int evictIntervalMillis = 5 * 60 * 1000;
29 |
30 | private final byte[] lock = new byte[0];
31 | private final LRUCache cache;
32 | private final PeriodicThread pThread;
33 | private int maxCount = 0;
34 |
35 | public void setMillis(long expired, long reserve, int evictInterval) {
36 | expiredMillis = expired;
37 | reserveMillis = reserve;
38 | usableDuration = expired - reserve;
39 | evictIntervalMillis = evictInterval;
40 | }
41 |
42 | public SessionManager() {
43 | cache = new LRUCache<>(Integer.MAX_VALUE >> 1);
44 | pThread = new PeriodicThread("SessionManagerThread", () -> {
45 | Map.Entry entry;
46 | SessionItem session;
47 | long nowTime = System.currentTimeMillis();
48 | synchronized (lock) {
49 | final Iterator> iterator = cache.entrySet().iterator();
50 | while (iterator.hasNext()) {
51 | entry = iterator.next();
52 | session = entry.getValue();
53 | session.uid = null;
54 | if (nowTime - session.startTimeMillis >= expiredMillis) {
55 | iterator.remove();
56 | } else {
57 | break;
58 | }
59 | }
60 | }
61 | }, evictIntervalMillis);
62 | pThread.start();
63 | }
64 |
65 | public static SessionManager self() {
66 | if (selfInstance == null) {
67 | synchronized (SessionManager.class) {
68 | if (selfInstance == null) {
69 | selfInstance = new SessionManager();
70 | }
71 | }
72 | }
73 | return selfInstance;
74 | }
75 |
76 | public void put(SessionItem session) {
77 | synchronized (lock) {
78 | cache.put(session.uid, session);
79 | if (cache.size() > maxCount) {
80 | maxCount = cache.size();
81 | }
82 | }
83 | }
84 |
85 | public void remove(String uid) {
86 | SessionItem session;
87 | synchronized (lock) {
88 | session = cache.remove(uid);
89 | }
90 | if (session != null) {
91 | session.uid = null;
92 | }
93 | }
94 |
95 | public int getCount() {
96 | return cache.size();
97 | }
98 |
99 | public int getMaxCount() {
100 | return maxCount;
101 | }
102 |
103 | public SessionItem get(String uid) {
104 | SessionItem session;
105 | synchronized (lock) {
106 | session = cache.get(uid);
107 | }
108 | return session;
109 | }
110 |
111 | public void renew(String uid) {
112 | SessionItem session = cache.get(uid);
113 | if (session != null) {
114 | session.startTimeMillis = System.currentTimeMillis();
115 | }
116 | }
117 |
118 | public boolean isUsable(String uid) {
119 | SessionItem item = get(uid);
120 | return item != null
121 | && (System.currentTimeMillis() - item.startTimeMillis < usableDuration);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/AsyncSqlExecutor.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database;
2 |
3 |
4 | import cn.liberg.core.OperatorException;
5 | import cn.liberg.core.PeriodicThread;
6 | import org.slf4j.Logger;
7 | import org.slf4j.LoggerFactory;
8 |
9 | import java.util.ArrayList;
10 | import java.util.LinkedList;
11 | import java.util.List;
12 |
13 | /**
14 | * @author Liberg
15 | *
16 | * 异步sql/save/update的执行线程,
17 | * 默认每3毫秒执行一次批量写数据库。
18 | */
19 | public class AsyncSqlExecutor {
20 | private static final Logger logger = LoggerFactory.getLogger(AsyncSqlExecutor.class);
21 |
22 | private static int BATCH_SIZE = 300;
23 | private static int INTERVAL_MILLIS = 3;
24 | private static int EXECUTE_SAVE = 1;
25 | private static int EXECUTE_UPDATE = 2;
26 | private static int EXECUTE_SQL = 4;
27 |
28 | private final LinkedList opList;
29 | private DBHelper dbHelper;
30 | private PeriodicThread periodicThread;
31 |
32 | public AsyncSqlExecutor(DBHelper dbController) {
33 | opList = new LinkedList<>();
34 | dbHelper = dbController;
35 |
36 | final List infos = new ArrayList<>();
37 | final List sqls = new ArrayList<>();
38 |
39 | periodicThread = new PeriodicThread("AsyncSqlExecutor", () -> {
40 | synchronized (opList) {
41 | while (opList.size() > 0 && infos.size() < BATCH_SIZE) {
42 | infos.add(opList.removeFirst());
43 | }
44 | }
45 | if (infos.size() <= 0) {
46 | try {
47 | Thread.sleep(INTERVAL_MILLIS);
48 | } catch (InterruptedException e) {
49 | logger.warn("AsyncSqlExecutor sleep interrupted...");
50 | }
51 | } else {
52 | String sql;
53 | try {
54 | for (OpInfo info : infos) {
55 | if (info.type == EXECUTE_SAVE) {
56 | sql = DBHelper.buildSaveSql(info.target, info.dao);
57 | } else if (info.type == EXECUTE_UPDATE) {
58 | sql = DBHelper.buildUpdateSql(info.target, info.dao);
59 | } else {
60 | sql = (String) info.target;
61 | }
62 | if(sql != null) {
63 | sqls.add(sql);
64 | }
65 | }
66 | dbHelper.executeSqlBatch(sqls);
67 | } catch (OperatorException e) {
68 | logger.error(e.getMessage(), e);
69 | }
70 | sqls.clear();
71 | infos.clear();
72 | }
73 | }, INTERVAL_MILLIS);
74 | }
75 |
76 | public void start() {
77 | periodicThread.start();
78 | }
79 |
80 | public void save(BaseDao dao, Object entity) {
81 | OpInfo info = new OpInfo(EXECUTE_SAVE, dao, entity);
82 | synchronized (opList) {
83 | opList.add(info);
84 | }
85 | }
86 |
87 | public void update(BaseDao dao, Object entity) {
88 | OpInfo info = new OpInfo(EXECUTE_UPDATE, dao, entity);
89 | synchronized (opList) {
90 | opList.add(info);
91 | }
92 | }
93 |
94 | public void executeSql(String sql) {
95 | final OpInfo info = new OpInfo(EXECUTE_SQL, null, sql);
96 | synchronized (opList) {
97 | opList.add(info);
98 | }
99 | }
100 |
101 | private static class OpInfo {
102 | private int type;
103 | private Object target;
104 | private BaseDao dao;
105 |
106 | public OpInfo(int type, BaseDao dao, Object target) {
107 | this.type = type;
108 | this.target = target;
109 | this.dao = dao;
110 | }
111 | }
112 |
113 | public int getWaitCount() {
114 | int count;
115 | synchronized (opList) {
116 | count = opList.size();
117 | }
118 | return count;
119 | }
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/PreparedSelect.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.Field;
5 | import cn.liberg.database.BaseDao;
6 |
7 | import java.sql.ResultSet;
8 | import java.sql.SQLException;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * Prepare方式select操作的入口类。
14 | *
15 | *
16 | * 1、查询哪张表,由构造方法的参数{@code dao}传入。
17 | * 2、通过{@code whereXxx(...)}系列方法将控制权转移给{@link PreparedSelectWhere}。
18 | *
19 | * @param
20 | * 代表查询结果实体类,如果查询数据表的所有列
21 | * 代表Segment,如果查询数据表的部分列
22 | * 代表String/Number,如果只查询数据表的某一列
23 | *
24 | * 子类包括:
25 | * {@link PreparedSelectColumn} 查询某一列
26 | * {@link PreparedSelectSegment} 查询部分列
27 | *
28 | * @author Liberg
29 | * @see PreparedSelectWhere
30 | */
31 | public class PreparedSelect {
32 | BaseDao dao;
33 |
34 | public PreparedSelect(BaseDao dao) {
35 | this.dao = dao;
36 | }
37 |
38 | protected StringBuilder build() {
39 | StringBuilder sb = new StringBuilder();
40 | sb.append("select ");
41 | appendColumns(sb);
42 | sb.append(" from ");
43 | sb.append(dao.getTableName());
44 | return sb;
45 | }
46 |
47 | protected void appendColumns(StringBuilder sb) {
48 | sb.append("id,");
49 | sb.append(dao.getColumnsString());
50 | }
51 |
52 | /**
53 | * column = ?
54 | */
55 | public PreparedSelectWhere whereEq$(Column column) {
56 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
57 | psw.eq$(column);
58 | return psw;
59 | }
60 | /**
61 | * column <> ?
62 | */
63 | public PreparedSelectWhere whereNe$(Column column) {
64 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
65 | psw.ne$(column);
66 | return psw;
67 | }
68 | /**
69 | * column like ?
70 | */
71 | public PreparedSelectWhere whereLike$(Column column) {
72 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
73 | psw.like$(column);
74 | return psw;
75 | }
76 | /**
77 | * column > ?:Number
78 | */
79 | public PreparedSelectWhere whereGt$(Field extends Number> column) {
80 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
81 | psw.gt$(column);
82 | return psw;
83 | }
84 | /**
85 | * column >= ?:Number
86 | */
87 | public PreparedSelectWhere whereGe$(Field extends Number> column) {
88 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
89 | psw.ge$(column);
90 | return psw;
91 | }
92 | /**
93 | * column < ?:Number
94 | */
95 | public PreparedSelectWhere whereLt$(Field extends Number> column) {
96 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
97 | psw.lt$(column);
98 | return psw;
99 | }
100 | /**
101 | * column <= ?:Number
102 | */
103 | public PreparedSelectWhere whereLe$(Field extends Number> column) {
104 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
105 | psw.le$(column);
106 | return psw;
107 | }
108 | /**
109 | * not - where后面的条件由not逻辑符开始
110 | */
111 | public PreparedSelectWhere whereNot() {
112 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
113 | psw.not();
114 | return psw;
115 | }
116 | /**
117 | * ( - where后面的条件由左括号开始
118 | */
119 | public PreparedSelectWhere whereBracketStart() {
120 | final PreparedSelectWhere psw = new PreparedSelectWhere(this);
121 | psw.bracketStart();
122 | return psw;
123 | }
124 |
125 | protected T readOne(ResultSet resultSet) throws SQLException {
126 | T entity = null;
127 | if(resultSet.next()) {
128 | entity = dao.buildEntity(resultSet);
129 | dao.putToCache(entity);
130 | }
131 | return entity;
132 | }
133 |
134 | protected List readAll(ResultSet resultSet) throws SQLException {
135 | List list = new ArrayList<>();
136 | while (resultSet.next()) {
137 | list.add(dao.buildEntity(resultSet));
138 | }
139 | return list;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/DigestUtils.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.security.MessageDigest;
6 | import java.security.NoSuchAlgorithmException;
7 |
8 | public abstract class DigestUtils {
9 | private static final String MD5_ALGORITHM_NAME = "MD5";
10 | private static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
11 |
12 | public DigestUtils() {
13 | }
14 |
15 | public static byte[] md5Digest(byte[] bytes) {
16 | return digest("MD5", bytes);
17 | }
18 |
19 | public static byte[] md5Digest(InputStream inputStream) throws IOException {
20 | return digest("MD5", inputStream);
21 | }
22 |
23 | public static String md5DigestAsHex(byte[] bytes) {
24 | return digestAsHexString("MD5", bytes);
25 | }
26 |
27 | public static String md5DigestAsHex(InputStream inputStream) throws IOException {
28 | return digestAsHexString("MD5", inputStream);
29 | }
30 |
31 | public static StringBuilder appendMd5DigestAsHex(byte[] bytes, StringBuilder builder) {
32 | return appendDigestAsHex("MD5", bytes, builder);
33 | }
34 |
35 | public static StringBuilder appendMd5DigestAsHex(InputStream inputStream, StringBuilder builder) throws IOException {
36 | return appendDigestAsHex("MD5", inputStream, builder);
37 | }
38 |
39 | private static MessageDigest getDigest(String algorithm) {
40 | try {
41 | return MessageDigest.getInstance(algorithm);
42 | } catch (NoSuchAlgorithmException var2) {
43 | throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + algorithm + "\"", var2);
44 | }
45 | }
46 |
47 | private static byte[] digest(String algorithm, byte[] bytes) {
48 | return getDigest(algorithm).digest(bytes);
49 | }
50 |
51 | private static byte[] digest(String algorithm, InputStream inputStream) throws IOException {
52 | MessageDigest messageDigest = getDigest(algorithm);
53 | if (inputStream instanceof UpdateMessageDigestInputStream) {
54 | ((UpdateMessageDigestInputStream)inputStream).updateMessageDigest(messageDigest);
55 | return messageDigest.digest();
56 | } else {
57 | byte[] buffer = new byte[4096];
58 | boolean var4 = true;
59 |
60 | int bytesRead;
61 | while((bytesRead = inputStream.read(buffer)) != -1) {
62 | messageDigest.update(buffer, 0, bytesRead);
63 | }
64 |
65 | return messageDigest.digest();
66 | }
67 | }
68 |
69 | private static String digestAsHexString(String algorithm, byte[] bytes) {
70 | char[] hexDigest = digestAsHexChars(algorithm, bytes);
71 | return new String(hexDigest);
72 | }
73 |
74 | private static String digestAsHexString(String algorithm, InputStream inputStream) throws IOException {
75 | char[] hexDigest = digestAsHexChars(algorithm, inputStream);
76 | return new String(hexDigest);
77 | }
78 |
79 | private static StringBuilder appendDigestAsHex(String algorithm, byte[] bytes, StringBuilder builder) {
80 | char[] hexDigest = digestAsHexChars(algorithm, bytes);
81 | return builder.append(hexDigest);
82 | }
83 |
84 | private static StringBuilder appendDigestAsHex(String algorithm, InputStream inputStream, StringBuilder builder) throws IOException {
85 | char[] hexDigest = digestAsHexChars(algorithm, inputStream);
86 | return builder.append(hexDigest);
87 | }
88 |
89 | private static char[] digestAsHexChars(String algorithm, byte[] bytes) {
90 | byte[] digest = digest(algorithm, bytes);
91 | return encodeHex(digest);
92 | }
93 |
94 | private static char[] digestAsHexChars(String algorithm, InputStream inputStream) throws IOException {
95 | byte[] digest = digest(algorithm, inputStream);
96 | return encodeHex(digest);
97 | }
98 |
99 | private static char[] encodeHex(byte[] bytes) {
100 | char[] chars = new char[32];
101 |
102 | for(int i = 0; i < chars.length; i += 2) {
103 | byte b = bytes[i / 2];
104 | chars[i] = HEX_CHARS[b >>> 4 & 15];
105 | chars[i + 1] = HEX_CHARS[b & 15];
106 | }
107 |
108 | return chars;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/support/data/dao/impl/UserDaoImpl.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.support.data.dao.impl;
2 |
3 | import cn.liberg.core.*;
4 | import cn.liberg.database.BaseDao;
5 | import cn.liberg.support.data.entity.Role;
6 | import cn.liberg.support.data.entity.User;
7 |
8 | import java.sql.PreparedStatement;
9 | import java.sql.ResultSet;
10 | import java.sql.SQLException;
11 | import java.util.ArrayList;
12 | import java.util.Arrays;
13 | import java.util.List;
14 |
15 | /**
16 | * 本文件由LibergCoder代码插件自动生成,请勿手动修改!!
17 | */
18 | public class UserDaoImpl extends BaseDao {
19 | public static final String TABLE_NAME = "user";
20 | public static final CachedColumn columnId = new CachedIdColumn(TenThousands.X1) {
21 | @Override
22 | public void set(User entity, Long value) {
23 | entity.id = value;
24 | }
25 | @Override
26 | public Long get(User entity) {
27 | return entity.id;
28 | }
29 | };
30 | public static final CachedStringColumn columnName = new CachedStringColumn("name", "n", 4){
31 | @Override
32 | public String get(User entity) {
33 | return entity.name;
34 | }
35 | @Override
36 | public void set(User entity, String value) {
37 | entity.name = value;
38 | }
39 | };
40 | public static final Column columnPassword = new StringColumn("password", "p") {
41 |
42 | @Override
43 | public void set(User entity, String value) {
44 | entity.password = value;
45 | }
46 |
47 | @Override
48 | public String get(User entity) {
49 | return entity.password;
50 | }
51 | };
52 | public static final Column columnAge = new ByteColumn("age", "a"){
53 |
54 | @Override
55 | public void set(User entity, Byte value) {
56 | entity.age = value;
57 | }
58 |
59 | @Override
60 | public Byte get(User entity) {
61 | return entity.age;
62 | }
63 | };
64 | public static final Column columnRoleId = new LongColumn("roleId", "ri"){
65 |
66 | @Override
67 | public void set(User entity, Long value) {
68 | entity.roleId = value;
69 | }
70 | @Override
71 | public Long get(User entity) {
72 | return entity.roleId;
73 | }
74 | };
75 | public static final CachedLongColumn columnCreateTime = new CachedLongColumn("createTime", "ct", 0){
76 |
77 | @Override
78 | public Long get(User entity) {
79 | return entity.createTime;
80 | }
81 |
82 | @Override
83 | public void set(User entity, Long value) {
84 | entity.createTime = value;
85 | }
86 | };
87 |
88 | public static final CachedColumnPair $name$createTime = new CachedColumnPair(columnName, columnCreateTime, 6);
89 |
90 | protected UserDaoImpl() {
91 | super(TABLE_NAME, Arrays.asList(
92 | columnId,
93 | columnName,
94 | $name$createTime
95 | ));
96 | }
97 |
98 | @Override
99 | public Class getEntityClazz() {
100 | return User.class;
101 | }
102 |
103 | @Override
104 | public final User buildEntity(ResultSet rs) throws SQLException {
105 | User entity = new User();
106 | entity.id = rs.getLong(1);
107 | entity.name = rs.getString(2);
108 | entity.password = rs.getString(3);
109 | entity.age = rs.getByte(4);
110 | entity.roleId = rs.getLong(5);
111 | entity.createTime = rs.getLong(6);
112 | return entity;
113 | }
114 |
115 | @Override
116 | protected final void fillPreparedStatement(User entity, PreparedStatement ps) throws SQLException {
117 | ps.setString(1, entity.name);
118 | ps.setString(2, entity.password);
119 | ps.setByte(3, entity.age);
120 | ps.setLong(4, entity.roleId);
121 | ps.setLong(5, entity.createTime);
122 | }
123 |
124 | @Override
125 | public Column getIdColumn() {
126 | return columnId;
127 | }
128 |
129 | @Override
130 | public final List initColumns() {
131 | List columns = new ArrayList<>(8);
132 | columns.add(columnName);
133 | columns.add(columnPassword);
134 | columns.add(columnAge);
135 | columns.add(columnRoleId);
136 | columns.add(columnCreateTime);
137 | return columns;
138 | }
139 | }
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/SelectWhere.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.OperatorException;
5 | import cn.liberg.core.StatusCode;
6 | import cn.liberg.database.BaseDao;
7 | import cn.liberg.database.DBHelper;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import java.sql.ResultSet;
12 | import java.sql.SQLException;
13 | import java.sql.Statement;
14 | import java.util.List;
15 |
16 | /**
17 | * 数据表查询操作的执行类。
18 | *
19 | * @param
20 | *
21 | * @author Liberg
22 | */
23 | public class SelectWhere extends Where> {
24 | private static final Logger logger = LoggerFactory.getLogger(DBHelper.class);
25 |
26 | private final Select select;
27 | private Column orderBy = null;
28 | private boolean isAsc = true;
29 |
30 | public SelectWhere(Select select) {
31 | this.select = select;
32 | }
33 |
34 | /**
35 | * 按指定column升序
36 | */
37 | public SelectWhere asc(Column column) {
38 | orderBy = column;
39 | isAsc = true;
40 | return this;
41 | }
42 |
43 | /**
44 | * 按指定column降序
45 | */
46 | public SelectWhere desc(Column column) {
47 | orderBy = column;
48 | isAsc = false;
49 | return this;
50 | }
51 |
52 | public T one() throws OperatorException {
53 | DBHelper dbHelper = BaseDao.dbHelper;
54 | Statement statement = dbHelper.createStatement();
55 | boolean isTxError = false;
56 | try {
57 | final StringBuilder sql = buildSql();
58 | sql.append(" limit 1;");
59 | ResultSet rs = statement.executeQuery(sql.toString());
60 | return select.readOne(rs);
61 | } catch (SQLException e) {
62 | isTxError = DBHelper.isTxError(e);
63 | throw new OperatorException(StatusCode.ERROR_DB, e);
64 | } finally {
65 | dbHelper.close(statement, isTxError);
66 | }
67 | }
68 |
69 | public List all(int limitCount) throws OperatorException {
70 | final StringBuilder sb = buildSql();
71 | sb.append(" limit ");
72 | sb.append(limitCount);
73 | return executeQuery(sb.toString());
74 | }
75 |
76 | public List allFill(int limitCount) throws OperatorException {
77 | List list = all(limitCount);
78 | for(T e : list) {
79 | select.dao.fillData(e);
80 | }
81 | return list;
82 | }
83 |
84 | public List page(int pageNum, int pageSize) throws OperatorException {
85 | final StringBuilder sql = buildSql();
86 | sql.append(" limit ");
87 | sql.append((pageNum - 1) * pageSize);
88 | sql.append(',');
89 | sql.append(pageSize);
90 | return executeQuery(sql.toString());
91 | }
92 |
93 | public List pageFill(int pageNum, int pageSize) throws OperatorException {
94 | List list = page(pageNum, pageSize);
95 | for (T e : list) {
96 | select.dao.fillData(e);
97 | }
98 | return list;
99 | }
100 |
101 | public int count() throws OperatorException {
102 | final StringBuilder where = new StringBuilder();
103 | appendConditionTo(where);
104 | return select.dao.getCount(where.toString());
105 | }
106 |
107 | private List executeQuery(String sql) throws OperatorException {
108 | if(logger.isDebugEnabled()) {
109 | logger.debug(sql);
110 | }
111 | System.out.println("query: " + sql);
112 | DBHelper dbHelper = BaseDao.dbHelper;
113 | Statement statement = dbHelper.createStatement();
114 | boolean isTxError = false;
115 | try {
116 | ResultSet rs = statement.executeQuery(sql);
117 | return select.readAll(rs);
118 | } catch (SQLException e) {
119 | isTxError = DBHelper.isTxError(e);
120 | throw new OperatorException(StatusCode.ERROR_DB, e);
121 | } finally {
122 | dbHelper.close(statement, isTxError);
123 | }
124 | }
125 |
126 | private StringBuilder buildSql() {
127 | StringBuilder sql = select.build();
128 | sql.append(" where ");
129 | appendConditionTo(sql);
130 | if (orderBy != null) {
131 | sql.append(" order by ");
132 | sql.append(orderBy.name);
133 | if (!isAsc) {
134 | sql.append(" desc ");
135 | }
136 | }
137 | return sql;
138 | }
139 |
140 | @Override
141 | public String toString() {
142 | return getClass().getSimpleName() + "{" + buildSql() + "}";
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/cache/TrieCache.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.cache;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * 通过HashMap实现的字典树Trie
7 | * 优点:速度快
8 | * 缺点:HashMap占用内存大
9 | * 适用于特定行业的词库,总词数不太大(比如3000以内,视可用内存而定)
10 | *
11 | * @author Liberg
12 | */
13 | public class TrieCache {
14 | private final Map root;
15 | private Set ignoredSet;
16 |
17 | public TrieCache() {
18 | root = new HashMap<>();
19 | ignoredSet = new HashSet<>();
20 | initIgnoredChars();
21 | }
22 |
23 | private void initIgnoredChars() {
24 | //默认过滤掉换行符、标点符号
25 | String ignore = "\r\n,.:;?!,。:;!";
26 | for(int i=0;i cur = root;
35 | for (int i = 0; i < keyword.length(); i++) {
36 | c = keyword.charAt(i);
37 | if (cur == null) {
38 | cur = new HashMap<>();
39 | unit.child = cur;
40 | }
41 | unit = cur.get(c);
42 | if (unit == null) {
43 | unit = new Unit(c, null);
44 | }
45 | cur.put(c, unit);
46 | cur = unit.child;
47 | if (i == keyword.length() - 1) {
48 | unit.keyword = keyword;
49 | }
50 | }
51 | }
52 |
53 | public void remove(String word) {
54 | char c;
55 | Unit unit = null;
56 | Stack stack = new Stack<>();
57 | Map cur = root;
58 | for (int i = 0; i < word.length(); i++) {
59 | c = word.charAt(i);
60 | if (cur == null) {
61 | break;
62 | }
63 | unit = cur.get(c);
64 | if (unit == null) {
65 | break;
66 | }
67 | stack.push(unit);
68 | cur = unit.child;
69 | }
70 | //完整地找到了要删的词
71 | if (stack.size() >= word.length()) {
72 | Unit leaf = stack.pop();
73 | leaf.keyword = null;
74 | Unit rm = leaf;
75 | Unit lastRm = null;
76 | boolean skip = false;
77 | while (stack.size() > 0) {
78 | lastRm = rm;
79 | rm = stack.pop();
80 | if (rm.child.size() <= 1) {
81 | rm.child = null;
82 | } else {
83 | skip = true;
84 | rm.child.remove(lastRm.c);
85 | break;
86 | }
87 | if(rm.keyword!=null) {
88 | break;
89 | }
90 | }
91 | if(!skip) {
92 | root.remove(rm.c);
93 | }
94 | }
95 | }
96 |
97 | public List match(String text) {
98 | List list = new ArrayList<>();
99 | char c;
100 | Unit unit = null;
101 | Map cur = root;
102 | for (int i = 0; i < text.length(); i++) {
103 | c = text.charAt(i);
104 | if(ignoredSet.contains(c)) {
105 | cur = root;
106 | continue;
107 | }
108 | unit = cur.get(c);
109 | if (unit != null) {
110 | cur = unit.child;
111 | if (cur == null) {
112 | cur = root;
113 | }
114 | if (unit.keyword != null) {
115 | list.add(unit.keyword);
116 | }
117 | } else {
118 | cur = root;
119 | }
120 | }
121 | return list;
122 | }
123 |
124 | public static void main(String[] args) {
125 | // String[] lexicon = {"二批商","促销","促销员","二维码"};
126 | String[] lexicon = {"二批商", "经销商", "促销", "促销员","促销人", "二维码", "经销门店", "门店", "门店二维码"};
127 | TrieCache cache = new TrieCache();
128 | for (int i = 0; i < lexicon.length; i++) {
129 | cache.put(lexicon[i]);
130 | }
131 | String test = "我们行业很缺少促销员,\r所以怎样呢?促销人员,经销\r\n门店可以使用吗?有没有二维码";
132 | List list = cache.match(test);
133 | for (String keyword : list) {
134 | System.out.println("find:" + keyword);
135 | }
136 | //remove test
137 | cache.remove("促销人");
138 | cache.remove("促销员");
139 | System.out.println("------------------");
140 | list = cache.match(test);
141 | for (String keyword : list) {
142 | System.out.println("find:" + keyword);
143 | }
144 |
145 | System.out.println("end");
146 | }
147 |
148 | private static class Unit {
149 | String keyword;
150 | Character c;
151 | Map child;
152 |
153 | public Unit(Character c, Map child) {
154 | this.keyword = null;
155 | this.c = c;
156 | this.child = child;
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/test/java/cn/liberg/DaoTest.java:
--------------------------------------------------------------------------------
1 | package cn.liberg;
2 |
3 | import cn.liberg.core.OperatorException;
4 | import cn.liberg.core.Segment;
5 | import cn.liberg.database.*;
6 | import cn.liberg.database.join.JoinQuery;
7 | import cn.liberg.database.join.JoinResult;
8 | import cn.liberg.database.query.JoinQueryTest;
9 | import cn.liberg.support.data.DBConfig;
10 | import cn.liberg.support.data.DBImpl;
11 | import cn.liberg.support.data.dao.UserDao;
12 | import cn.liberg.support.data.entity.User;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | public class DaoTest {
20 |
21 | @Before
22 | public void testInit() {
23 | IDataBaseConf dbConfig = new DBConfig();
24 | DBHelper.self().init(new DBImpl(dbConfig));
25 | }
26 |
27 | private User buildTestUser() {
28 | User user = new User();
29 | user.createTime = System.currentTimeMillis();
30 | user.name = "";
31 | user.password = "1";
32 | user.roleId = 1;
33 | return user;
34 | }
35 |
36 | private void deleteUserByName(String name) throws OperatorException {
37 | DBHelper.self().delete(UserDao.self(),
38 | UserDao.columnName.name+ Condition.EQ+ SqlDefender.format(name));
39 | }
40 |
41 | // 没有使用事务
42 | private void doSth() throws OperatorException {
43 | final int userCount = 3;
44 | User user = buildTestUser();
45 | UserDao dao = UserDao.self();
46 | String userName;
47 | for (int i = 0; i < userCount; i++) {
48 | userName = "Transact" + i;
49 | // 先删除,如果存在的话
50 | deleteUserByName(userName);
51 |
52 | user.age = (byte) i;
53 | user.name = userName;
54 | // 再添加
55 | dao.save(user);
56 | }
57 | }
58 |
59 | @Test
60 | public void testJoinQuery() throws OperatorException {
61 | final JoinQuery joinQuery = JoinQueryTest.testJoinQuery();
62 | JoinResult datas = joinQuery.all();
63 | System.out.println(datas);
64 | }
65 |
66 | @Test
67 | public void testTransaction() throws OperatorException {
68 | DBHelper.self().beginTransact();
69 | try {
70 | // 使用DBHelper提供的事务接口
71 | doSth();
72 | DBHelper.self().endTransact();
73 | } catch (Exception e) {
74 | DBHelper.self().transactRollback();
75 | e.printStackTrace();
76 | }
77 | }
78 |
79 | @Test
80 | public void testTransaction2() throws OperatorException {
81 | final int userCount = 3;
82 | User user = buildTestUser();
83 | UserDao dao = UserDao.self();
84 |
85 | final String result = dao.transaction(() -> {
86 | String userName;
87 | for (int i = 0; i < userCount; i++) {
88 | userName = "Transact" + i;
89 | // 先删除,如果存在的话
90 | deleteUserByName(userName);
91 |
92 | user.age = (byte) i;
93 | user.name = userName;
94 | // 再添加
95 | dao.save(user);
96 | }
97 | return "succ";
98 | });
99 | System.out.println(result);
100 | }
101 |
102 | @Test
103 | public void testTransaction3() throws OperatorException {
104 | UserDao dao = UserDao.self();
105 | dao.transaction(this::doSth);
106 | String result = dao.transaction(()->{doSth();return "11";});
107 | System.out.println(result);
108 | }
109 |
110 |
111 | @Test
112 | public void test_saveOrUpdateBatch() throws OperatorException {
113 | final int userCount = 10;
114 | String userName;
115 | UserDao dao = UserDao.self();
116 | List list = new ArrayList<>(userCount);
117 | for (int i = 0; i < userCount; i++) {
118 | userName = "saveOrUpdateBatch_" + i;
119 | User user = dao.getByName1_getEq(userName);
120 | if(user == null) {
121 | user = buildTestUser();
122 | user.name = userName;
123 | } else {
124 | user.password = "newPassword";
125 | }
126 | list.add(user);
127 | }
128 |
129 | DBHelper.self().saveOrUpdateBatch(UserDao.self(), list);
130 | }
131 |
132 | @Test
133 | public void testGetAllUsers() throws OperatorException {
134 | UserDao dao = UserDao.self();
135 | List users = dao.getGts(dao.columnId, 0, 100);
136 |
137 | }
138 |
139 | @Test
140 | public void test_SegementQuery() throws OperatorException {
141 | UserDao dao = UserDao.self();
142 |
143 | Segment userSegment = dao.select(dao.columnId, UserDao.columnName)
144 | .whereGt(UserDao.columnAge, 0).one();
145 | System.out.println(userSegment);
146 | long id = userSegment.get(dao.columnId);
147 | String name = userSegment.get(UserDao.columnName);
148 | System.out.println("id="+id);
149 | System.out.println("name="+name);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/select/Select.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.select;
2 |
3 | import cn.liberg.core.Field;
4 | import cn.liberg.database.BaseDao;
5 |
6 | import java.sql.ResultSet;
7 | import java.sql.SQLException;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | /**
12 | * select操作的入口类。
13 | *
14 | *
15 | * 1、查询哪张表,由构造方法的参数{@code dao}传入。
16 | * 2、通过{@code whereXxx(...)}系列方法将控制权转移给{@link SelectWhere}。
17 | *
18 | * @param
19 | * 代表查询结果实体类,如果查询数据表的所有列
20 | * 代表Segment,如果查询数据表的部分列
21 | * 代表String/Number,如果只查询数据表的某一列
22 | *
23 | * 子类包括:
24 | * {@link SelectColumn} 查询某一列
25 | * {@link SelectSegment} 查询部分列
26 | *
27 | * @author Liberg
28 | * @see SelectWhere
29 | */
30 | public class Select {
31 | BaseDao dao;
32 |
33 | public Select(BaseDao dao) {
34 | this.dao = dao;
35 | }
36 |
37 | protected StringBuilder build() {
38 | StringBuilder sql = new StringBuilder(256);
39 | sql.append("select ");
40 | appendColumnsTo(sql);
41 | sql.append(" from ");
42 | sql.append(dao.getTableName());
43 | return sql;
44 | }
45 |
46 | protected void appendColumnsTo(StringBuilder sql) {
47 | sql.append("id,");
48 | sql.append(dao.getColumnsString());
49 | }
50 |
51 | /**
52 | * 1 = 1
53 | */
54 | public SelectWhere where() {
55 | final SelectWhere selectWhere = new SelectWhere(this);
56 | return selectWhere;
57 | }
58 | /**
59 | * column = value:String
60 | */
61 | public SelectWhere whereEq(Field column, String value) {
62 | final SelectWhere selectWhere = new SelectWhere(this);
63 | selectWhere.eq(column, value);
64 | return selectWhere;
65 | }
66 | /**
67 | * column = value:Number
68 | */
69 | public SelectWhere whereEq(Field extends Number> column, Number value) {
70 | final SelectWhere selectWhere = new SelectWhere(this);
71 | selectWhere.eq(column, value);
72 | return selectWhere;
73 | }
74 | /**
75 | * column <> value:String
76 | */
77 | public SelectWhere whereNe(Field column, String value) {
78 | final SelectWhere selectWhere = new SelectWhere(this);
79 | selectWhere.ne(column, value);
80 | return selectWhere;
81 | }
82 | /**
83 | * column <> value:Number
84 | */
85 | public SelectWhere whereNe(Field extends Number> column, Number value) {
86 | final SelectWhere selectWhere = new SelectWhere(this);
87 | selectWhere.ne(column, value);
88 | return selectWhere;
89 | }
90 | /**
91 | * column like value:String
92 | */
93 | public SelectWhere whereLike(Field column, String value) {
94 | final SelectWhere selectWhere = new SelectWhere(this);
95 | selectWhere.like(column, value);
96 | return selectWhere;
97 | }
98 | /**
99 | * column > value:Number
100 | */
101 | public SelectWhere whereGt(Field extends Number> column, Number value) {
102 | final SelectWhere selectWhere = new SelectWhere(this);
103 | selectWhere.gt(column, value);
104 | return selectWhere;
105 | }
106 | /**
107 | * column >= value:Number
108 | */
109 | public SelectWhere whereGe(Field extends Number> column, Number value) {
110 | final SelectWhere selectWhere = new SelectWhere(this);
111 | selectWhere.ge(column, value);
112 | return selectWhere;
113 | }
114 | /**
115 | * column < value:Number
116 | */
117 | public SelectWhere whereLt(Field extends Number> column, Number value) {
118 | final SelectWhere selectWhere = new SelectWhere(this);
119 | selectWhere.lt(column, value);
120 | return selectWhere;
121 | }
122 | /**
123 | * column <= value:Number
124 | */
125 | public SelectWhere whereLe(Field extends Number> column, Number value) {
126 | final SelectWhere selectWhere = new SelectWhere(this);
127 | selectWhere.le(column, value);
128 | return selectWhere;
129 | }
130 | /**
131 | * not - where后面的条件由not逻辑符开始
132 | */
133 | public SelectWhere whereNot() {
134 | final SelectWhere selectWhere = new SelectWhere(this);
135 | selectWhere.not();
136 | return selectWhere;
137 | }
138 | /**
139 | * ( - where后面的条件由左括号开始
140 | */
141 | public SelectWhere whereBracketStart() {
142 | final SelectWhere selectWhere = new SelectWhere(this);
143 | selectWhere.bracketStart();
144 | return selectWhere;
145 | }
146 |
147 | public T readOne(ResultSet resultSet) throws SQLException {
148 | T entity = null;
149 | if(resultSet.next()) {
150 | entity = dao.buildEntity(resultSet);
151 | dao.putToCache(entity);
152 | }
153 | return entity;
154 | }
155 |
156 | public List readAll(ResultSet resultSet) throws SQLException {
157 | List list = new ArrayList<>();
158 | while (resultSet.next()) {
159 | list.add(dao.buildEntity(resultSet));
160 | }
161 | return list;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/main/java/cn/liberg/database/update/Update.java:
--------------------------------------------------------------------------------
1 | package cn.liberg.database.update;
2 |
3 | import cn.liberg.core.Column;
4 | import cn.liberg.core.Field;
5 | import cn.liberg.database.BaseDao;
6 | import cn.liberg.database.SqlDefender;
7 |
8 | import java.util.LinkedHashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * update操作的入口类。
13 | *
14 | *
15 | * 1、更新哪张表,由构造方法的参数{@code dao}传入。
16 | * 2、通过{@code set(...)}、{@code increment(...)}方法,指定将哪些列更新为什么值。
17 | * 3、通过{@code whereXxx(...)}系列方法将控制权转移给{@link UpdateWhere}。
18 | *
19 | * @param 代表实体类的泛型参数
20 | *
21 | * @author Liberg
22 | * @see UpdateWhere
23 | */
24 | public class Update {
25 | LinkedHashMap pairs;
26 | BaseDao dao;
27 |
28 | public Update(BaseDao dao) {
29 | this.dao = dao;
30 | pairs = new LinkedHashMap<>(16);
31 | }
32 |
33 | public Update set(Field column, String value) {
34 | pairs.put(column, SqlDefender.format(value));
35 | return this;
36 | }
37 |
38 | public Update set(Field extends Number> column, Number value) {
39 | pairs.put(column, value.toString());
40 | return this;
41 | }
42 |
43 | public Update increment(Field extends Number> column, int value) {
44 | StringBuilder sb = new StringBuilder(column.name);
45 | if (value >= 0) {
46 | sb.append('+');
47 | }
48 | sb.append(value);
49 | pairs.put(column, sb.toString());
50 | return this;
51 | }
52 |
53 | String build() {
54 | StringBuilder sb = new StringBuilder();
55 | for(Map.Entry entry : pairs.entrySet()) {
56 | sb.append(entry.getKey().name);
57 | sb.append('=');
58 | sb.append(entry.getValue());
59 | sb.append(',');
60 | }
61 |
62 | if(sb.length() > 0) {
63 | sb.deleteCharAt(sb.length()-1);
64 | }
65 | return sb.toString();
66 | }
67 |
68 | /**
69 | * 1 = 1
70 | */
71 | public UpdateWhere where() {
72 | final UpdateWhere updateWhere = new UpdateWhere(this);
73 | return updateWhere;
74 | }
75 | /**
76 | * column = value:String
77 | */
78 | public UpdateWhere whereEq(Field column, String value) {
79 | final UpdateWhere updateWhere = new UpdateWhere(this);
80 | updateWhere.eq(column, value);
81 | return updateWhere;
82 | }
83 | /**
84 | * column = value:Number
85 | */
86 | public UpdateWhere whereEq(Field extends Number> column, Number value) {
87 | final UpdateWhere updateWhere = new UpdateWhere(this);
88 | updateWhere.eq(column, value);
89 | return updateWhere;
90 | }
91 | /**
92 | * column <> value:String
93 | */
94 | public UpdateWhere whereNe(Field column, String value) {
95 | final UpdateWhere updateWhere = new UpdateWhere(this);
96 | updateWhere.ne(column, value);
97 | return updateWhere;
98 | }
99 | /**
100 | * column <> value:Number
101 | */
102 | public UpdateWhere