├── .idea ├── .gitignore ├── dictionaries │ └── liny.xml ├── vcs.xml ├── encodings.xml ├── misc.xml ├── compiler.xml ├── inspectionProfiles │ └── Project_Default.xml └── uiDesigner.xml ├── Liberg.iml ├── .gitignore ├── src ├── main │ └── java │ │ └── cn │ │ └── liberg │ │ ├── core │ │ ├── ICacheEntity.java │ │ ├── IdColumn.java │ │ ├── IStatusCode.java │ │ ├── CachedIdColumn.java │ │ ├── ByteColumn.java │ │ ├── LongColumn.java │ │ ├── IntegerColumn.java │ │ ├── CachedByteColumn.java │ │ ├── CachedLongColumn.java │ │ ├── StringColumn.java │ │ ├── CachedStringColumn.java │ │ ├── TenThousands.java │ │ ├── Segment.java │ │ ├── IdMaker.java │ │ ├── OperatorException.java │ │ ├── Column.java │ │ ├── PeriodicThread.java │ │ ├── CachedColumn.java │ │ ├── CachedColumnPair.java │ │ ├── StatusCode.java │ │ ├── CachedColumnTrio.java │ │ ├── Field.java │ │ └── Response.java │ │ ├── database │ │ ├── TransactionCall.java │ │ ├── TransactionCallWithResult.java │ │ ├── select │ │ │ ├── NextIndex.java │ │ │ ├── NextIndex1.java │ │ │ ├── NextIndexN.java │ │ │ ├── SelectColumn.java │ │ │ ├── PreparedSelectColumn.java │ │ │ ├── PreparedSelectWhere.java │ │ │ ├── SelectSegment.java │ │ │ ├── PreparedSelectSegment.java │ │ │ ├── Where.java │ │ │ ├── PreparedSelect.java │ │ │ ├── SelectWhere.java │ │ │ ├── Select.java │ │ │ ├── PreparedWhere.java │ │ │ └── PreparedSelectExecutor.java │ │ ├── join │ │ │ ├── JoinResult.java │ │ │ ├── JoinDao.java │ │ │ ├── JoinWhere.java │ │ │ ├── JoinOn.java │ │ │ ├── JoinFields.java │ │ │ └── JoinCondition.java │ │ ├── IDataReader.java │ │ ├── IDataBaseConf.java │ │ ├── Joints.java │ │ ├── SqlDefender.java │ │ ├── WhereMeta.java │ │ ├── update │ │ │ ├── UpdateWhere.java │ │ │ └── Update.java │ │ ├── TableBuilder.java │ │ ├── IDataBase.java │ │ ├── TableAlteration.java │ │ ├── Condition.java │ │ ├── DBVersion.java │ │ ├── DBVersionManager.java │ │ ├── AsyncSqlExecutor.java │ │ └── DBConnector.java │ │ ├── session │ │ ├── AbstractSession.java │ │ ├── SessionItem.java │ │ └── SessionManager.java │ │ ├── cache │ │ ├── ICache.java │ │ ├── ConcurrentLRUCache.java │ │ ├── LRUCache.java │ │ └── TrieCache.java │ │ └── annotation │ │ ├── cache.java │ │ └── dbmap.java └── test │ └── java │ └── cn │ └── liberg │ ├── support │ ├── data │ │ ├── entity │ │ │ ├── Role.java │ │ │ └── User.java │ │ ├── dao │ │ │ ├── RoleDao.java │ │ │ ├── impl │ │ │ │ ├── RoleDaoImpl.java │ │ │ │ └── UserDaoImpl.java │ │ │ └── UserDao.java │ │ ├── DBConfig.java │ │ ├── DBUpgrader.java │ │ ├── DBInitializer.java │ │ └── DBImpl.java │ ├── UpdateMessageDigestInputStream.java │ ├── RandomString.java │ └── DigestUtils.java │ ├── MainTest.java │ ├── database │ ├── query │ │ ├── ColumnTest.java │ │ └── JoinQueryTest.java │ ├── SqlDefenderTest.java │ ├── TableBuilderTest.java │ └── DBConnectorTest.java │ ├── DaoTest.java │ └── UserDaoTest.java ├── LICENSE └── pom.xml /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /Liberg.iml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/dictionaries/liny.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | liberg 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/classes 2 | /target/surefire-reports 3 | /target/test-classes 4 | /target/generated-sources 5 | /target/generated-test-sources 6 | /target/maven-archiver 7 | /target/maven-status 8 | /.idea 9 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/ICacheEntity.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | public interface ICacheEntity { 4 | public void put(E entity); 5 | public void remove(E entity); 6 | public void clear(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/TransactionCall.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database; 2 | 3 | import cn.liberg.core.OperatorException; 4 | 5 | @FunctionalInterface 6 | public interface TransactionCall { 7 | public void execute() throws OperatorException; 8 | } -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/TransactionCallWithResult.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database; 2 | 3 | import cn.liberg.core.OperatorException; 4 | 5 | @FunctionalInterface 6 | public interface TransactionCallWithResult { 7 | public R execute() throws OperatorException; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/select/NextIndex.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.select; 2 | 3 | /** 4 | * PreparedStatement中某个column=? 5 | * next()方法返回占位符的序号(是第几个占位符) 6 | */ 7 | interface NextIndex { 8 | public int next(); 9 | public NextIndex add(int newIndex); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/IdColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | /** 4 | * 数据库表的id列映射到实体的id成员 5 | * 6 | * @author Liberg 7 | */ 8 | public abstract class IdColumn extends LongColumn { 9 | 10 | public IdColumn() { 11 | super(ID, ID); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/join/JoinResult.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.join; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 多表join查询的结果数据 7 | * 8 | * @author Liberg 9 | */ 10 | public class JoinResult { 11 | public String[] heads; 12 | public List datas; 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/cn/liberg/support/data/entity/Role.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.support.data.entity; 2 | 3 | import cn.liberg.annotation.dbmap; 4 | 5 | public class Role { 6 | public long id; 7 | @dbmap(length=31) 8 | public String name; 9 | @dbmap(length=5000) 10 | public String permissions; 11 | } -------------------------------------------------------------------------------- /src/main/java/cn/liberg/session/AbstractSession.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.session; 2 | 3 | /** 4 | * 抽象的Session类 5 | * 定制化的Session类从此类继承 6 | * 7 | * @author Liberg 8 | */ 9 | public abstract class AbstractSession { 10 | 11 | /** 12 | * Unique session ID 13 | */ 14 | public String uid; 15 | } 16 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/IStatusCode.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | /** 4 | * 错误状态码接口 5 | * 6 | * @author Liberg 7 | * @see OperatorException 8 | */ 9 | public interface IStatusCode { 10 | 11 | /** 12 | * @return 状态码 13 | */ 14 | public int code(); 15 | 16 | /** 17 | * @return 状态描述 18 | */ 19 | public String desc(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/join/JoinDao.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.join; 2 | 3 | 4 | import cn.liberg.database.BaseDao; 5 | 6 | /** 7 | * join操作的参与方 8 | * 9 | * @author Liberg 10 | */ 11 | public class JoinDao { 12 | public final String alias; 13 | public final BaseDao dao; 14 | 15 | public JoinDao(BaseDao dao, String alias) { 16 | this.dao = dao; 17 | this.alias = alias; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/IDataReader.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | import java.util.List; 6 | 7 | /** 8 | * @author Liberg 9 | */ 10 | @FunctionalInterface 11 | public interface IDataReader { 12 | /** 13 | * 由实现类完成从ResultSet中读取记录 14 | * @param resultSet 15 | * @return 16 | * @throws SQLException 17 | */ 18 | void read(ResultSet resultSet) throws SQLException; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/CachedIdColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | public abstract class CachedIdColumn extends CachedColumn { 7 | 8 | public CachedIdColumn(int capacity) { 9 | super(ID, ID, capacity); 10 | } 11 | 12 | public CachedIdColumn() { 13 | this(0); 14 | } 15 | 16 | public Long getValue(ResultSet rs, int columnIndex) throws SQLException { 17 | return rs.getLong(columnIndex); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/select/NextIndex1.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.select; 2 | 3 | /** 4 | * PreparedStatement中某个column只出现了一次 5 | * next()方法返回占位符的序号(是第几个占位符) 6 | */ 7 | class NextIndex1 implements NextIndex { 8 | private final int index; 9 | 10 | public NextIndex1(int index) { 11 | this.index = index; 12 | } 13 | 14 | public NextIndexN add(int newIndex) { 15 | return new NextIndexN(this, newIndex); 16 | } 17 | 18 | public int next() { 19 | return index; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/cn/liberg/support/data/dao/RoleDao.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.support.data.dao; 2 | 3 | import cn.liberg.support.data.dao.impl.RoleDaoImpl; 4 | 5 | public class RoleDao extends RoleDaoImpl { 6 | private static volatile RoleDao _instance; 7 | 8 | public static RoleDao self() { 9 | if (_instance == null) { 10 | synchronized (RoleDao.class) { 11 | if (_instance == null) { 12 | _instance = new RoleDao(); 13 | } 14 | } 15 | } 16 | return _instance; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/ByteColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * 实体byte字段-->数据库TINYINT列 8 | * 9 | * @author Liberg 10 | */ 11 | public abstract class ByteColumn extends Column { 12 | 13 | public ByteColumn(String entityFieldName, String shortName) { 14 | super(entityFieldName, shortName); 15 | } 16 | 17 | @Override 18 | public Byte getValue(ResultSet rs, int columnIndex) throws SQLException { 19 | return rs.getByte(columnIndex); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/LongColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * 实体long字段-->数据库BIGINT列 8 | * 9 | * @author Liberg 10 | */ 11 | public abstract class LongColumn extends Column { 12 | 13 | public LongColumn(String entityFieldName, String shortName) { 14 | super(entityFieldName, shortName); 15 | } 16 | 17 | @Override 18 | public Long getValue(ResultSet rs, int columnIndex) throws SQLException { 19 | return rs.getLong(columnIndex); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/IntegerColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * 实体int字段-->数据库INT列 8 | * 9 | * @author Liberg 10 | */ 11 | public abstract class IntegerColumn extends Column { 12 | 13 | public IntegerColumn(String entityFieldName, String shortName) { 14 | super(entityFieldName, shortName); 15 | } 16 | 17 | @Override 18 | public Integer getValue(ResultSet rs, int columnIndex) throws SQLException { 19 | return rs.getInt(columnIndex); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/CachedByteColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | public abstract class CachedByteColumn extends CachedColumn { 7 | 8 | public CachedByteColumn(String entityFieldName, String shortName, int capacity) { 9 | super(entityFieldName, shortName, capacity); 10 | } 11 | 12 | public CachedByteColumn(String entityFieldName, String shortName) { 13 | super(entityFieldName, shortName); 14 | } 15 | 16 | public Byte getValue(ResultSet rs, int columnIndex) throws SQLException { 17 | return rs.getByte(columnIndex); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/CachedLongColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | public abstract class CachedLongColumn extends CachedColumn { 7 | 8 | public CachedLongColumn(String entityFieldName, String shortName, int capacity) { 9 | super(entityFieldName, shortName, capacity); 10 | } 11 | 12 | public CachedLongColumn(String entityFieldName, String shortName) { 13 | super(entityFieldName, shortName); 14 | } 15 | 16 | public Long getValue(ResultSet rs, int columnIndex) throws SQLException { 17 | return rs.getLong(columnIndex); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/StringColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * 实体String字段--> 8 | * 数据库VARCHAR列,长度小于4096时 9 | * 数据库TEXT列,长度达到/超过4096时 10 | * 11 | * @author Liberg 12 | */ 13 | public abstract class StringColumn extends Column { 14 | 15 | public StringColumn(String entityFieldName, String shortName) { 16 | super(entityFieldName, shortName); 17 | } 18 | 19 | @Override 20 | public String getValue(ResultSet rs, int columnIndex) throws SQLException { 21 | return rs.getString(columnIndex); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/CachedStringColumn.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | public abstract class CachedStringColumn extends CachedColumn { 7 | 8 | public CachedStringColumn(String entityFieldName, String shortName, int capacity) { 9 | super(entityFieldName, shortName, capacity); 10 | } 11 | 12 | public CachedStringColumn(String entityFieldName, String shortName) { 13 | super(entityFieldName, shortName); 14 | } 15 | 16 | public String getValue(ResultSet rs, int columnIndex) throws SQLException { 17 | return rs.getString(columnIndex); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/cn/liberg/MainTest.java: -------------------------------------------------------------------------------- 1 | package cn.liberg; 2 | 3 | import cn.liberg.database.DBHelper; 4 | import cn.liberg.database.IDataBaseConf; 5 | import cn.liberg.database.query.JoinQueryTest; 6 | import cn.liberg.support.data.DBConfig; 7 | import cn.liberg.support.data.DBImpl; 8 | import org.junit.BeforeClass; 9 | import org.junit.runner.RunWith; 10 | import org.junit.runners.Suite; 11 | 12 | @RunWith(Suite.class) 13 | @Suite.SuiteClasses({UserDaoTest.class, DaoTest.class}) 14 | public class MainTest { 15 | 16 | @BeforeClass 17 | public static void testInit() { 18 | IDataBaseConf dbConfig = new DBConfig(); 19 | DBHelper.self().init(new DBImpl(dbConfig)); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/cache/ICache.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.cache; 2 | 3 | /** 4 | * 抽象缓存接口 5 | * @param 6 | * @param 7 | */ 8 | public interface ICache { 9 | /** 10 | * 以字符串key读取缓存对象 11 | * @param key 12 | * @return 13 | */ 14 | public V get(K key); 15 | 16 | /** 17 | * 通过字符串key将对象写入缓存 18 | * @param key 19 | * @param obj 20 | */ 21 | public void put(K key, V obj); 22 | 23 | /** 24 | * 通过字符串key移除缓存对象 25 | * @param key 26 | */ 27 | public void remove(K key); 28 | 29 | /** 30 | * 清空所有缓存对象 31 | */ 32 | public void clear(); 33 | 34 | /** 35 | * 缓存对象的总个数 36 | * @return 37 | */ 38 | public int size(); 39 | 40 | /** 41 | * 缓存的最大容量 42 | * @return 43 | */ 44 | public int capacity(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/TenThousands.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | public class TenThousands { 4 | public static final int X1 = 10000; 5 | public static final int X2 = X1 * 2; 6 | public static final int X3 = X1 * 3; 7 | public static final int X4 = X1 * 4; 8 | public static final int X5 = X1 * 5; 9 | public static final int X10 = X1 * 10; 10 | public static final int X15 = X1 * 15; 11 | public static final int X20 = X1 * 20; 12 | public static final int X25 = X1 * 25; 13 | public static final int X30 = X1 * 30; 14 | public static final int X35 = X1 * 35; 15 | public static final int X40 = X1 * 40; 16 | public static final int X45 = X1 * 45; 17 | public static final int X50 = X1 * 50; 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/cn/liberg/database/query/ColumnTest.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.query; 2 | 3 | import cn.liberg.core.Column; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class ColumnTest { 9 | 10 | @Test 11 | public void testParseColumnName() { 12 | assertEquals("id", Column.parseColumnName("id")); 13 | assertEquals("_user_name", Column.parseColumnName("userName")); 14 | assertEquals("_user_name", Column.parseColumnName("mUserName")); 15 | assertEquals("_user_ppps_name", Column.parseColumnName("mUserPPPsName")); 16 | assertEquals("_name", Column.parseColumnName("name")); 17 | assertEquals("_mobile", Column.parseColumnName("mobile")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/session/SessionItem.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.session; 2 | 3 | import cn.liberg.core.IdMaker; 4 | 5 | /** 6 | * @author Liberg 7 | */ 8 | public class SessionItem { 9 | public String uid; 10 | public long startTimeMillis; 11 | 12 | private AbstractSession session; 13 | private static IdMaker idMaker = new IdMaker(); 14 | 15 | public SessionItem() { 16 | this.uid = idMaker.nextUid(); 17 | this.startTimeMillis = System.currentTimeMillis(); 18 | } 19 | 20 | public SessionItem(AbstractSession session) { 21 | this(); 22 | session.uid = this.uid; 23 | this.session = session; 24 | } 25 | 26 | public T getSession() { 27 | return (T)session; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/select/NextIndexN.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.select; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * PreparedStatement中某个column只出现了不止一次 8 | * next()方法返回下一个占位符的序号(是第几个占位符) 9 | */ 10 | class NextIndexN implements NextIndex { 11 | private int _i = -1; 12 | private final List list; 13 | 14 | public NextIndexN(NextIndex1 old, int newIndex) { 15 | list = new ArrayList<>(); 16 | list.add(old.next()); 17 | list.add(newIndex); 18 | } 19 | 20 | public NextIndexN add(int newIndex) { 21 | list.add(newIndex); 22 | return this; 23 | } 24 | 25 | public int next() { 26 | _i = (_i+1)%list.size(); 27 | return list.get(_i); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/join/JoinWhere.java: -------------------------------------------------------------------------------- 1 | 2 | package cn.liberg.database.join; 3 | 4 | import cn.liberg.database.WhereMeta; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class JoinWhere { 10 | protected List whereMetas = new ArrayList<>(); 11 | 12 | public String build() { 13 | StringBuilder sb = new StringBuilder(); 14 | for(WhereMeta m : whereMetas) { 15 | sb.append(m.value); 16 | sb.append(" "); 17 | } 18 | return sb.toString(); 19 | } 20 | 21 | public JoinWhere add(WhereMeta meta) { 22 | whereMetas.add(meta); 23 | return this; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return getClass().getName()+"{"+build()+"}"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/cn/liberg/database/SqlDefenderTest.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database; 2 | 3 | import org.junit.Test; 4 | import static org.junit.Assert.assertEquals; 5 | 6 | public class SqlDefenderTest { 7 | 8 | @Test 9 | public void testFormat() { 10 | /** 11 | * 常见的sql注入方式 12 | * 1、 select * from user where _name='' or 1=1#' and _password='abc' 13 | * _name传入"' or 1=1#" 14 | * 15 | * 2、 select * from user where _name='admin' or '1' and _password='admin' or '1' 16 | * _name和_password都传入"admin' or '1" 17 | */ 18 | String test; 19 | 20 | test = "' or 1=1#"; 21 | assertEquals(SqlDefender.format(test), "'\\' or 1=1#'"); 22 | test = "admin' or '1"; 23 | assertEquals(SqlDefender.format(test), "'admin\\' or \\'1'"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/core/Segment.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.core; 2 | 3 | import cn.liberg.database.BaseDao; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * 只查询一张表的部分字段时,查出的一条记录会映射到一个{@link Segment}对象, 9 | * 而不是相应的entity。 10 | * 11 | *

12 | * 通过{@code get(Column column)}方法,可以拿到column列对应的值。 13 | *

14 | * 15 | * 指代相应的实体类型 16 | * @author Liberg 17 | */ 18 | public class Segment extends HashMap { 19 | 20 | public Segment(BaseDao dao) { 21 | super(8); 22 | } 23 | 24 | public Segment(BaseDao dao, int initialCapacity) { 25 | super(initialCapacity); 26 | } 27 | 28 | public F get(Field 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 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 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 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 column, Number value) { 61 | return _add(column, Condition.GE, "" + value); 62 | } 63 | 64 | public T gt(Field 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 column, Number value) { 72 | return _add(column, Condition.LE, "" + value); 73 | } 74 | 75 | public T lt(Field 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 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 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 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 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 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 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 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 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 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 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 column, Number value) { 39 | pairs.put(column, value.toString()); 40 | return this; 41 | } 42 | 43 | public Update increment(Field 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 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 whereNe(Field column, Number value) { 103 | final UpdateWhere updateWhere = new UpdateWhere(this); 104 | updateWhere.ne(column, value); 105 | return updateWhere; 106 | } 107 | /** 108 | * column like value:String 109 | */ 110 | public UpdateWhere whereLike(Field column, String value) { 111 | final UpdateWhere updateWhere = new UpdateWhere(this); 112 | updateWhere.like(column, value); 113 | return updateWhere; 114 | } 115 | /** 116 | * column > value:Number 117 | */ 118 | public UpdateWhere whereGt(Field column, Number value) { 119 | final UpdateWhere updateWhere = new UpdateWhere(this); 120 | updateWhere.gt(column, value); 121 | return updateWhere; 122 | } 123 | /** 124 | * column >= value:Number 125 | */ 126 | public UpdateWhere whereGe(Field column, Number value) { 127 | final UpdateWhere updateWhere = new UpdateWhere(this); 128 | updateWhere.ge(column, value); 129 | return updateWhere; 130 | } 131 | /** 132 | * column < value:Number 133 | */ 134 | public UpdateWhere whereLt(Field column, Number value) { 135 | final UpdateWhere updateWhere = new UpdateWhere(this); 136 | updateWhere.lt(column, value); 137 | return updateWhere; 138 | } 139 | /** 140 | * column <= value:Number 141 | */ 142 | public UpdateWhere whereLe(Field column, Number value) { 143 | final UpdateWhere updateWhere = new UpdateWhere(this); 144 | updateWhere.le(column, value); 145 | return updateWhere; 146 | } 147 | /** 148 | * not - where后面的条件由not逻辑符开始 149 | */ 150 | public UpdateWhere whereNot() { 151 | final UpdateWhere updateWhere = new UpdateWhere(this); 152 | updateWhere.not(); 153 | return updateWhere; 154 | } 155 | /** 156 | * ( - where后面的条件由左括号开始 157 | */ 158 | public UpdateWhere whereBracketStart() { 159 | final UpdateWhere updateWhere = new UpdateWhere(this); 160 | updateWhere.bracketStart(); 161 | return updateWhere; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/select/PreparedWhere.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.SqlDefender; 6 | import cn.liberg.database.Condition; 7 | import cn.liberg.database.Joints; 8 | import cn.liberg.database.WhereMeta; 9 | 10 | import java.util.ArrayList; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * Prepare方式的where条件查询 17 | * 18 | * @author Liberg 19 | */ 20 | public class PreparedWhere { 21 | protected List criteriaList = new ArrayList<>(); 22 | protected Map indexMap = new HashMap<>(); 23 | // 占位符 24 | public static final String $ = "?"; 25 | // 占位符的个数 26 | protected int $length = 0; 27 | 28 | protected StringBuilder buildCondition() { 29 | StringBuilder sb = new StringBuilder(); 30 | if(criteriaList.size() > 0) { 31 | for (WhereMeta e : criteriaList) { 32 | sb.append(e.value); 33 | sb.append(" "); 34 | } 35 | } else { 36 | sb.append("1=1 "); 37 | } 38 | return sb; 39 | } 40 | 41 | /** 42 | * equals or not 43 | */ 44 | public T eq(Field column, String value) { 45 | return _add(column, Condition.EQ, SqlDefender.format(value)); 46 | } 47 | 48 | public T eq(Field column, Number value) { 49 | return _add(column, Condition.EQ, "" + value); 50 | } 51 | 52 | public T ne(Field column, String value) { 53 | return _add(column, Condition.NE, SqlDefender.format(value)); 54 | } 55 | 56 | public T ne(Field column, Number value) { 57 | return _add(column, Condition.NE, "" + value); 58 | } 59 | 60 | /** 61 | * 带'$'后缀的方法,用于提醒开发者,当前列置入了一个占位符'?', 62 | * 在实际执行sql之前,需要调用{@code setParameter}进行参数填充。 63 | */ 64 | public T eq$(Column column) { 65 | return _add(column, Condition.EQ); 66 | } 67 | 68 | public T ne$(Column column) { 69 | return _add(column, Condition.NE); 70 | } 71 | 72 | /** 73 | * like 74 | */ 75 | public T like(Field column, String value) { 76 | return _add(column, Condition.LIKE, SqlDefender.format(value)); 77 | } 78 | 79 | public T like$(Field column) { 80 | return _add(column, Condition.LIKE); 81 | } 82 | 83 | /** 84 | * great equal or great than 85 | */ 86 | public T ge(Field column, Number value) { 87 | return _add(column, Condition.GE, "" + value); 88 | } 89 | 90 | public T gt(Field column, Number value) { 91 | return _add(column, Condition.GT, "" + value); 92 | } 93 | 94 | public T ge$(Field column) { 95 | return _add(column, Condition.GE); 96 | } 97 | public T gt$(Field column) { 98 | return _add(column, Condition.GT); 99 | } 100 | 101 | /** 102 | * less equal or less than 103 | */ 104 | public T le(Field column, Number value) { 105 | return _add(column, Condition.LE, "" + value); 106 | } 107 | 108 | public T lt(Field column, Number value) { 109 | return _add(column, Condition.LT, "" + value); 110 | } 111 | 112 | public T le$(Field column) { 113 | return _add(column, Condition.LE); 114 | } 115 | public T lt$(Field column) { 116 | return _add(column, Condition.LT); 117 | } 118 | 119 | /** 120 | * and/or/not 121 | */ 122 | public T and() { 123 | criteriaList.add(Joints.AND); 124 | return (T) this; 125 | } 126 | 127 | public T or() { 128 | criteriaList.add(Joints.OR); 129 | return (T) this; 130 | } 131 | 132 | public T not() { 133 | criteriaList.add(Joints.NOT); 134 | return (T) this; 135 | } 136 | 137 | public T bracketStart() { 138 | criteriaList.add(Joints.BRACKET_START); 139 | return (T) this; 140 | } 141 | 142 | public T bracketEnd() { 143 | criteriaList.add(Joints.BRACKET_END); 144 | return (T) this; 145 | } 146 | 147 | protected T _add(Field column, String link, String value) { 148 | // 两个Condition之间未指定逻辑运算符时,默认用and 149 | if (criteriaList.size() > 0 && criteriaList.get(criteriaList.size() - 1).isCondition()) { 150 | criteriaList.add(Joints.AND); 151 | } 152 | criteriaList.add(new Condition(column.name, link, value)); 153 | return (T) this; 154 | } 155 | 156 | //value用占位符代替 157 | protected T _add(Field column, String link) { 158 | // 两个Condition之间未指定逻辑运算符时,默认用and 159 | if (criteriaList.size() > 0 && criteriaList.get(criteriaList.size() - 1).isCondition()) { 160 | criteriaList.add(Joints.AND); 161 | } 162 | criteriaList.add(new Condition(column.name, link, $)); 163 | //index是从1开始的,先++ 164 | $length++; 165 | NextIndex nIdx = indexMap.get(column.name); 166 | if(nIdx == null) { 167 | indexMap.put(column.name, new NextIndex1($length)); 168 | } else { 169 | indexMap.put(column.name, nIdx.add($length)); 170 | } 171 | return (T) this; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/select/PreparedSelectExecutor.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.database.select; 2 | 3 | import cn.liberg.core.Column; 4 | import cn.liberg.core.Field; 5 | import cn.liberg.core.OperatorException; 6 | import cn.liberg.core.StatusCode; 7 | import cn.liberg.database.DBHelper; 8 | 9 | import java.sql.PreparedStatement; 10 | import java.sql.ResultSet; 11 | import java.sql.SQLException; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * Prepare方式select的实际执行类。 17 | * 18 | * @param 19 | * @author Liberg 20 | */ 21 | public class PreparedSelectExecutor implements AutoCloseable { 22 | private final PreparedSelect select; 23 | private PreparedStatement preparedStatement; 24 | private Map indexMap; 25 | //除去 limit ?,?部分,占位符的个数 26 | private final int $length; 27 | 28 | public PreparedSelectExecutor(PreparedSelect select, PreparedSelectWhere selectWhere) throws OperatorException { 29 | this.select = select; 30 | preparedStatement = DBHelper.self().prepareStatement(selectWhere.buildSql()); 31 | indexMap = selectWhere.indexMap; 32 | $length = selectWhere.$length; 33 | } 34 | 35 | @Override 36 | public void close() { 37 | // 前面都执行成功了,可以认为跟MySQL服务端通信是没问题的 38 | // 因此,close第二个参数传入false 39 | DBHelper.self().close(preparedStatement, false); 40 | preparedStatement = null; 41 | } 42 | 43 | public void close(boolean isTxError) { 44 | DBHelper.self().close(preparedStatement, isTxError); 45 | preparedStatement = null; 46 | } 47 | 48 | public void setParameter(Field column, String value) throws OperatorException { 49 | NextIndex nIdx = indexMap.get(column.name); 50 | if (nIdx == null) { 51 | throw new OperatorException(StatusCode.PARAMS_INVALID, "Column:" + column.name + " is not prepared."); 52 | } 53 | try { 54 | preparedStatement.setString(nIdx.next(), value); 55 | } catch (SQLException e) { 56 | close(); 57 | throw new OperatorException(StatusCode.ERROR_DB, e); 58 | } 59 | } 60 | 61 | public void setParameter(Field column, byte value) throws OperatorException { 62 | NextIndex nIdx = indexMap.get(column.name); 63 | if (nIdx == null) { 64 | throw new OperatorException(StatusCode.PARAMS_INVALID, "Column:" + column.name + " is not prepared."); 65 | } 66 | try { 67 | preparedStatement.setByte(nIdx.next(), value); 68 | } catch (SQLException e) { 69 | close(); 70 | throw new OperatorException(StatusCode.ERROR_DB, e); 71 | } 72 | } 73 | 74 | public void setParameter(Field column, int value) throws OperatorException { 75 | NextIndex nIdx = indexMap.get(column.name); 76 | if (nIdx == null) { 77 | throw new OperatorException(StatusCode.PARAMS_INVALID, "Column:" + column.name + " is not prepared."); 78 | } 79 | try { 80 | preparedStatement.setInt(nIdx.next(), value); 81 | } catch (SQLException e) { 82 | close(); 83 | throw new OperatorException(StatusCode.ERROR_DB, e); 84 | } 85 | } 86 | 87 | public void setParameter(Field column, long value) throws OperatorException { 88 | NextIndex nIdx = indexMap.get(column.name); 89 | if (nIdx == null) { 90 | throw new OperatorException(StatusCode.PARAMS_INVALID, "Column:" + column.name + " is not prepared."); 91 | } 92 | try { 93 | preparedStatement.setLong(nIdx.next(), value); 94 | } catch (SQLException e) { 95 | close(); 96 | throw new OperatorException(StatusCode.ERROR_DB, e); 97 | } 98 | } 99 | 100 | private void setLimit(long limitStart, int limitCount) throws OperatorException { 101 | try { 102 | preparedStatement.setLong($length + 1, limitStart); 103 | preparedStatement.setInt($length + 2, limitCount); 104 | } catch (SQLException e) { 105 | close(); 106 | throw new OperatorException(StatusCode.ERROR_DB, e); 107 | } 108 | } 109 | 110 | private void setLimit(int limitCount) throws OperatorException { 111 | setLimit(0, limitCount); 112 | } 113 | 114 | 115 | public T one() throws OperatorException { 116 | setLimit(0, 1); 117 | try { 118 | ResultSet rs = preparedStatement.executeQuery(); 119 | return select.readOne(rs); 120 | } catch (SQLException e) { 121 | close(DBHelper.isTxError(e)); 122 | throw new OperatorException(StatusCode.ERROR_DB, e); 123 | } 124 | } 125 | 126 | private List allWithLimit(int limitStart, int limitCount) throws OperatorException { 127 | setLimit(limitStart, limitCount); 128 | try { 129 | ResultSet rs = preparedStatement.executeQuery(); 130 | return select.readAll(rs); 131 | } catch (SQLException e) { 132 | close(DBHelper.isTxError(e)); 133 | throw new OperatorException(StatusCode.ERROR_DB, e); 134 | } 135 | } 136 | 137 | public List all(int limitCount) throws OperatorException { 138 | return allWithLimit(0, limitCount); 139 | } 140 | 141 | public List page(int pageNum, int pageSize) throws OperatorException { 142 | return allWithLimit((pageNum - 1) * pageSize, pageSize); 143 | } 144 | 145 | public List allFill(int limitCount) throws OperatorException { 146 | List list = allWithLimit(0, limitCount); 147 | for (T e : list) { 148 | select.dao.fillData(e); 149 | } 150 | return list; 151 | } 152 | 153 | public List pageFill(int pageNum, int pageSize) throws OperatorException { 154 | List list = allWithLimit((pageNum - 1) * pageSize, pageSize); 155 | for (T e : list) { 156 | select.dao.fillData(e); 157 | } 158 | return list; 159 | } 160 | 161 | } 162 | -------------------------------------------------------------------------------- /src/test/java/cn/liberg/support/data/dao/UserDao.java: -------------------------------------------------------------------------------- 1 | package cn.liberg.support.data.dao; 2 | 3 | import cn.liberg.core.OperatorException; 4 | import cn.liberg.core.Segment; 5 | import cn.liberg.database.BaseDao; 6 | import cn.liberg.database.SqlDefender; 7 | import cn.liberg.database.select.PreparedSelectExecutor; 8 | import cn.liberg.database.select.PreparedSelectWhere; 9 | import cn.liberg.database.select.SelectWhere; 10 | import cn.liberg.support.data.dao.impl.UserDaoImpl; 11 | import cn.liberg.support.data.entity.User; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * 实际开发中,XxxDao类由LibergCoder IDEA插件进行创建和维护, 18 | * 开发人员只需要新增具体的业务方法。 19 | */ 20 | public class UserDao extends UserDaoImpl { 21 | private static volatile UserDao selfInstance; 22 | 23 | 24 | 25 | 26 | @Override 27 | protected void afterConstructed() { 28 | System.out.println("----------------- UserDao afterConstructed ------------"); 29 | } 30 | 31 | /** 32 | * 演示返回数据给客户端之前进行{@link cn.liberg.annotation.dbmap#isIndex} 33 | * {@code @dbmap(isIndex=false}字段的填充, 34 | * 或者进行其他额外处理。 35 | * 36 | * @param entity 37 | * @return 38 | * @throws OperatorException 39 | */ 40 | @Override 41 | public User fillData(User entity) throws OperatorException { 42 | if (entity.role == null) { 43 | entity.role = RoleDao.self().getById(entity.roleId); 44 | } 45 | return entity; 46 | } 47 | 48 | /** 49 | * update演示 50 | * 51 | * @throws OperatorException 52 | */ 53 | public void update(long id, String newName, String newPassword, int ageIncrement) throws OperatorException { 54 | update().set(columnName, newName) 55 | .set(columnPassword, newPassword) 56 | .increment(columnAge, ageIncrement) 57 | .whereEq(columnId, id) 58 | .execute(); 59 | } 60 | 61 | /** 62 | * 查询方式1 63 | *

64 | * 单一条件、单条记录查询,可以直接调用BaseDao中的getXx系列方法 65 | */ 66 | public User getByName1_getEq(String name) throws OperatorException { 67 | final User user = getEq(columnName, name); 68 | return fillData(user); 69 | } 70 | 71 | public User getBy$name$createTime(String name, long createTime) throws OperatorException { 72 | final User user = getEq($name$createTime, name, createTime); 73 | return fillData(user); 74 | } 75 | 76 | public User getBy_name_createTime(String name, long createTime) throws OperatorException { 77 | final User user = select() 78 | .whereEq(columnName, name) 79 | .eq(columnCreateTime, createTime) 80 | .desc(columnId) 81 | .one(); 82 | return fillData(user); 83 | } 84 | 85 | /** 86 | * 查询方式2 87 | *

88 | * prepare方式的条件查询 89 | */ 90 | public User getByName2_prepareSelect(String name) throws OperatorException { 91 | User user = null; 92 | 93 | final PreparedSelectWhere prepareSelect = prepareSelect() 94 | .whereEq$(columnName) 95 | .asc(columnId); 96 | 97 | final PreparedSelectExecutor prepare = prepareSelect.prepare(); 98 | 99 | // 可以复用prepare进行多次查询 100 | prepare.setParameter(columnName, name); 101 | user = prepare.one(); 102 | 103 | // 避免手动调用close,推荐try-with-resources写法 104 | prepare.close(); 105 | 106 | return user; 107 | } 108 | 109 | /** 110 | * 查询方式3 111 | *

112 | * 普通查询 113 | */ 114 | public User getByName3_select(String name) throws OperatorException { 115 | return select() 116 | .whereEq(columnName, name) 117 | .or() 118 | .eq(columnPassword, "123") 119 | .gt(columnAge, 30) 120 | .one(); 121 | } 122 | 123 | /** 124 | * 查询方式4 125 | *

126 | * String.format构建where条件,然后通过{@link BaseDao#getAll}进行查询 127 | */ 128 | public User getByName4_StringFormat(String name) throws OperatorException { 129 | String where = "%1$s=%2$s and %3$s>%4$s"; 130 | where = String.format(where, columnName.name, SqlDefender.format(name), columnId.name, 0); 131 | return getOne(where); 132 | } 133 | 134 | /** 135 | * 只查询某一列 136 | */ 137 | public List getUserNameList(String name) throws OperatorException { 138 | // whereGt(columnId, 0),即id>0这个条件当然是没必要的,这里只是为了演示 139 | final List list = select(columnName) 140 | .whereGt(columnId, 0) 141 | .eq(columnName, name) 142 | .asc(columnId) 143 | .all(10); 144 | return list; 145 | } 146 | 147 | /** 148 | * 只查询一条记录的某一列 149 | */ 150 | public String getUserName(String name) throws OperatorException { 151 | return select(columnName) 152 | .whereEq(columnName, name) 153 | .asc(columnId) 154 | .one(); 155 | } 156 | 157 | /** 158 | * 查询某些列 159 | *

160 | * 本例演示查询name和password两列 161 | */ 162 | public Segment getUserSegment(String name) throws OperatorException { 163 | final SelectWhere> selectWhere = select(columnName, columnPassword) 164 | .whereEq(columnName, name); 165 | return selectWhere.one(); 166 | } 167 | 168 | /** 169 | * 查询某些列 170 | *

171 | * 本例演示查询name和password两列 172 | */ 173 | public Segment getUserSegment_Prepared(String name) throws OperatorException { 174 | final PreparedSelectWhere> preparedSelectWhere = prepareSelect(columnName, columnPassword).whereEq$(columnName); 175 | final PreparedSelectExecutor> prepare = preparedSelectWhere.prepare(); 176 | final Segment one = prepare.one(); 177 | prepare.close(); 178 | 179 | return one; 180 | } 181 | 182 | public static UserDao self() { 183 | if (selfInstance == null) { 184 | synchronized (UserDao.class) { 185 | if (selfInstance == null) { 186 | selfInstance = new UserDao(); 187 | } 188 | } 189 | } 190 | return selfInstance; 191 | } 192 | 193 | } -------------------------------------------------------------------------------- /src/test/java/cn/liberg/UserDaoTest.java: -------------------------------------------------------------------------------- 1 | package cn.liberg; 2 | 3 | import cn.liberg.core.OperatorException; 4 | import cn.liberg.core.Segment; 5 | import cn.liberg.database.DBHelper; 6 | import cn.liberg.database.IDataBaseConf; 7 | import cn.liberg.support.DigestUtils; 8 | import cn.liberg.support.RandomString; 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.BeforeClass; 14 | import org.junit.Test; 15 | 16 | import java.sql.PreparedStatement; 17 | import java.sql.SQLException; 18 | import java.util.List; 19 | import java.util.concurrent.CountDownLatch; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertTrue; 23 | 24 | public class UserDaoTest { 25 | public static final String PWD_SALT = "$high_performance_liberg$"; 26 | public UserDao userDao = UserDao.self(); 27 | 28 | public static String getMd5WithSalt(String source) { 29 | return DigestUtils.md5DigestAsHex((source + PWD_SALT).getBytes()); 30 | } 31 | 32 | @BeforeClass 33 | public static void testInit() { 34 | IDataBaseConf dbConfig = new DBConfig(); 35 | DBHelper.self().init(new DBImpl(dbConfig)); 36 | } 37 | 38 | 39 | private static class InsertThread extends Thread { 40 | final CountDownLatch latch; 41 | final PreparedStatement statement; 42 | final int code; 43 | 44 | InsertThread(CountDownLatch latch, PreparedStatement statement, int code) { 45 | super(); 46 | this.latch = latch; 47 | this.statement = statement; 48 | this.code = code; 49 | } 50 | 51 | @Override 52 | public void run() { 53 | try { 54 | statement.setString(1, "name"+code); 55 | statement.setLong(2, 1000 + code); 56 | if(code == 1) { 57 | /** 58 | * 让其中一个线程休眠200ms 59 | * 预期结果是插入的两条记录都是 name2 1002 60 | */ 61 | Thread.currentThread().sleep(200); 62 | } 63 | statement.executeUpdate(); 64 | latch.countDown(); 65 | } catch (Exception e) { 66 | e.printStackTrace(); 67 | } 68 | } 69 | } 70 | 71 | @Test 72 | public void testMultiThread() throws OperatorException { 73 | String sql; 74 | sql ="INSERT INTO user(_name,_password,_age,_role_id,_create_time) VALUES (?, '', 100, 1, ?)"; 75 | PreparedStatement statement = DBHelper.self().prepareStatement(sql); 76 | 77 | CountDownLatch latch = new CountDownLatch(2); 78 | 79 | Thread t1 = new InsertThread(latch, statement, 1); 80 | Thread t2 = new InsertThread(latch, statement, 2); 81 | t1.start(); 82 | t2.start(); 83 | 84 | try { 85 | /** 86 | * 等待2个线程执行结束 87 | */ 88 | latch.await(); 89 | } catch (InterruptedException e) { 90 | e.printStackTrace(); 91 | } 92 | try { 93 | statement.close(); 94 | } catch (SQLException e) { 95 | e.printStackTrace(); 96 | } 97 | } 98 | 99 | 100 | @Test 101 | public void testAsync() throws Exception { 102 | long nowMills = System.currentTimeMillis(); 103 | 104 | /** 105 | * 测试异步save 106 | */ 107 | User user = new User(); 108 | user.name = "Aysnc_"+nowMills; 109 | user.password = "1"; 110 | user.roleId = 1; 111 | user.createTime = nowMills; 112 | user.age = 100; 113 | userDao.asyncSave(user); 114 | Thread.sleep(50); 115 | /** 116 | * 检查是否保存成功 117 | */ 118 | User selectedUser = userDao.getByName2_prepareSelect(user.name); 119 | assertEquals(selectedUser.age, user.age); 120 | assertEquals(selectedUser.name, user.name); 121 | 122 | /** 123 | * 异步update 124 | */ 125 | selectedUser.age = 10; 126 | selectedUser.name = "AysncUpdated_"+nowMills; 127 | userDao.asyncUpdate(selectedUser); 128 | Thread.sleep(50); 129 | /** 130 | * 检查是否更新成功 131 | */ 132 | User updatedUser = userDao.getByName3_select(selectedUser.name); 133 | assertEquals(updatedUser.age, selectedUser.age); 134 | assertEquals(updatedUser.name, selectedUser.name); 135 | } 136 | 137 | @Test 138 | public void testBasic() throws Exception { 139 | RandomString randStr = new RandomString(20, 20); 140 | 141 | /** 142 | * 构造一个随机用户名、密码的user 143 | */ 144 | final String userName = randStr.next(); 145 | final String password = getMd5WithSalt(randStr.next(8, 16)); 146 | User user = new User(); 147 | user.name = userName; 148 | user.password = password; 149 | user.age = 30; 150 | user.roleId = 1L; 151 | user.createTime = System.currentTimeMillis(); 152 | 153 | /** 154 | * 保存user到数据库 155 | */ 156 | userDao.save(user); 157 | 158 | User selectedUser; 159 | /** 160 | * 查询方式1 161 | */ 162 | selectedUser = userDao.getByName1_getEq(userName); 163 | assertEquals(selectedUser.name, userName); 164 | /** 165 | * 查询方式2 166 | */ 167 | selectedUser = userDao.getByName2_prepareSelect(userName); 168 | assertEquals(selectedUser.name, userName); 169 | /** 170 | * 查询方式3 171 | */ 172 | selectedUser = userDao.getByName3_select(userName); 173 | assertEquals(selectedUser.name, userName); 174 | /** 175 | * 查询方式4 176 | */ 177 | selectedUser = userDao.getByName4_StringFormat(userName); 178 | assertEquals(selectedUser.name, userName); 179 | 180 | /** 181 | * 只查询user表的用户名一列 182 | */ 183 | final List userNames = userDao.getUserNameList(userName); 184 | assertEquals(userNames.get(0), userName); 185 | /** 186 | * 只查询user表某条记录的用户名 187 | */ 188 | final String selectedUserName = userDao.getUserName(userName); 189 | assertEquals(selectedUserName, userName); 190 | /** 191 | * 只查询用户名、密码这两列 192 | */ 193 | Segment userSegment = userDao.getUserSegment(userName); 194 | String keyName = UserDao.columnName.shortName; 195 | String keyPassword = UserDao.columnPassword.shortName; 196 | assertEquals(userSegment.get(keyName), userName); 197 | assertEquals(userSegment.get(keyPassword), password); 198 | System.out.println(userSegment); 199 | 200 | /** 201 | * Prepare方式:只查询用户名、密码这两列 202 | */ 203 | userSegment = userDao.getUserSegment(userName); 204 | keyName = UserDao.columnName.shortName; 205 | keyPassword = UserDao.columnPassword.shortName; 206 | assertEquals(userSegment.get(keyName), userName); 207 | assertEquals(userSegment.get(keyPassword), password); 208 | System.out.println(userSegment); 209 | 210 | /** 211 | * 更新用户名、密码,年龄减10岁 212 | */ 213 | long id = selectedUser.id; 214 | String newMd5Password = getMd5WithSalt("12345"); 215 | String newName = "王五"+selectedUser.name; 216 | int age = selectedUser.age; 217 | int ageIncrement = -10; 218 | userDao.update(id, newName, newMd5Password, ageIncrement); 219 | 220 | User updatedUser = userDao.getEq(userDao.columnId, id); 221 | assertEquals(updatedUser.name, newName); 222 | assertEquals(updatedUser.password, newMd5Password); 223 | assertEquals(updatedUser.age, age + ageIncrement); 224 | } 225 | 226 | } -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 97 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/cn/liberg/database/DBConnector.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.Connection; 8 | import java.sql.DriverManager; 9 | import java.sql.SQLException; 10 | import java.sql.Statement; 11 | import java.util.ArrayList; 12 | import java.util.Date; 13 | import java.util.HashMap; 14 | 15 | /** 16 | * 一个小巧的数据库连接池实现 17 | * 18 | * @author Liberg 19 | */ 20 | public class DBConnector { 21 | private static final Logger logger = LoggerFactory.getLogger(DBConnector.class); 22 | 23 | private static int MAX_CONNECTION_COUNT = 1200; 24 | private static int MAX_CONNECTION_FREE_TIME = 1 * 60 * 60 * 1000; 25 | 26 | /** 27 | * 保留空闲的连接 28 | */ 29 | private final ArrayList freeList; 30 | 31 | /** 32 | * 处于事务中的连接单独保存到Map 33 | * 一个事务的开始、提交、回滚必须在同一个连接中完成 34 | */ 35 | private final HashMap inTransactionMap; 36 | private IDataBaseConf dbConf; 37 | private String connectUrl; 38 | /** 39 | * 当前连接数,包括空闲连接 40 | */ 41 | private volatile int connectCount = 0; 42 | /** 43 | * 连接数达到的上限 44 | */ 45 | private volatile int maxCount = 0; 46 | 47 | public DBConnector() { 48 | freeList = new ArrayList<>(); 49 | inTransactionMap = new HashMap<>(); 50 | } 51 | 52 | private class ConnectionInfo { 53 | public Connection connection; 54 | public Long lastUseTime; 55 | } 56 | 57 | public int getConnectCount() { 58 | return connectCount; 59 | } 60 | 61 | public int getFreeCount() { 62 | int freeCount; 63 | synchronized (freeList) { 64 | freeCount = freeList.size(); 65 | } 66 | return freeCount; 67 | } 68 | 69 | public int getMaxConnectCount() { 70 | return maxCount; 71 | } 72 | 73 | public void init(IDataBaseConf conf) { 74 | dbConf = conf; 75 | 76 | String url = dbConf.getUrl(); 77 | StringBuilder sb = new StringBuilder(256); 78 | sb.append(url); 79 | if (!url.endsWith("/")) { 80 | sb.append('/'); 81 | } 82 | sb.append(dbConf.getDbName()); 83 | sb.append("?useSSL=false&serverTimezone=UTC&characterEncoding="); 84 | sb.append(dbConf.getCharset()); 85 | connectUrl = sb.toString(); 86 | 87 | // Load jdbc driver class, and try to create database if absent 88 | tryConnect(dbConf); 89 | } 90 | 91 | 92 | public Connection getConnect() throws SQLException { 93 | Connection con; 94 | String threadId = Long.toString(Thread.currentThread().getId()); 95 | synchronized (inTransactionMap) { 96 | con = inTransactionMap.get(threadId); 97 | } 98 | if (con == null) { 99 | synchronized (freeList) { 100 | while (freeList.size() > 0) { 101 | ConnectionInfo conInfo = freeList.remove(0); 102 | //free too much 103 | while (freeList.size() > 200) { 104 | ConnectionInfo freeConInfo = freeList.remove(0); 105 | freeConnection(freeConInfo.connection, true); 106 | } 107 | if (!conInfo.connection.isClosed()) { 108 | long now = (new Date()).getTime(); 109 | if (now - conInfo.lastUseTime <= MAX_CONNECTION_FREE_TIME) { 110 | con = conInfo.connection; 111 | break; 112 | } else { 113 | freeConnection(conInfo.connection, true); 114 | } 115 | } 116 | } 117 | if (con == null) { 118 | if (connectCount < MAX_CONNECTION_COUNT) { 119 | con = DriverManager.getConnection(connectUrl, dbConf.getUserName(), dbConf.getPassword()); 120 | connectCount++; 121 | if (connectCount > maxCount) { 122 | maxCount = connectCount; 123 | } 124 | } else { 125 | throw new SQLException("Max connection count limited: " + MAX_CONNECTION_COUNT); 126 | } 127 | } 128 | } 129 | } 130 | return con; 131 | } 132 | 133 | private void tryConnect(IDataBaseConf conf) { 134 | try { 135 | Class.forName(conf.getDriverName()); 136 | } catch (Exception e) { 137 | throw new IllegalArgumentException("Failed to load jdbc driver: " + conf.getDriverName(), e); 138 | } 139 | 140 | SQLException exception = null; 141 | try { 142 | Connection conn = DriverManager.getConnection(connectUrl, dbConf.getUserName(), dbConf.getPassword()); 143 | conn.close(); 144 | } catch (SQLException e) { 145 | exception = e; 146 | } 147 | //1049 Unknown database 148 | if (exception != null && exception.getErrorCode() == MysqlErrorNumbers.ER_BAD_DB_ERROR) { 149 | String dbName = conf.getDbName(); 150 | 151 | //kick off the database name 152 | String url = connectUrl.replace('/' + dbName + '?', "/?"); 153 | Connection conn = null; 154 | Statement stat = null; 155 | try { 156 | conn = DriverManager.getConnection(url, dbConf.getUserName(), dbConf.getPassword()); 157 | stat = conn.createStatement(); 158 | createDatabase(stat, conf); 159 | logger.info("Database created: {}", dbName); 160 | } catch (SQLException e) { 161 | throw new IllegalArgumentException("Failed to connect: " + url); 162 | } finally { 163 | try { 164 | if (conn != null) { 165 | stat.close(); 166 | } 167 | if (stat != null) { 168 | conn.close(); 169 | } 170 | } catch (SQLException e) { 171 | } 172 | } 173 | } 174 | } 175 | 176 | public void freeConnection(Connection connection, boolean forceClose) { 177 | if (connection == null) { 178 | return; 179 | } 180 | final String threadId = Long.toString(Thread.currentThread().getId()); 181 | synchronized (inTransactionMap) { 182 | if (inTransactionMap.containsKey(threadId)) { 183 | // 处于事务中的连接应由endTransact/transactRollback完成释放 184 | return; 185 | } 186 | } 187 | synchronized (freeList) { 188 | if (forceClose) { 189 | try { 190 | connection.close(); 191 | connectCount--; 192 | } catch (SQLException e) { 193 | logger.error("Connection close error", e); 194 | } 195 | } else { 196 | ConnectionInfo info = new ConnectionInfo(); 197 | info.connection = connection; 198 | info.lastUseTime = System.currentTimeMillis(); 199 | freeList.add(info); 200 | } 201 | } 202 | } 203 | 204 | public void beginTransact() throws SQLException { 205 | Connection connect = getConnect(); 206 | connect.setAutoCommit(false); 207 | final String threadId = Long.toString(Thread.currentThread().getId()); 208 | synchronized (inTransactionMap) { 209 | inTransactionMap.put(threadId, connect); 210 | } 211 | } 212 | 213 | public void transactRollback() throws SQLException { 214 | Connection con = null; 215 | final String threadId = Long.toString(Thread.currentThread().getId()); 216 | synchronized (inTransactionMap) { 217 | con = inTransactionMap.remove(threadId); 218 | } 219 | if (con != null) { 220 | try { 221 | con.rollback(); 222 | } finally { 223 | con.setAutoCommit(true); 224 | freeConnection(con, false); 225 | } 226 | } 227 | } 228 | 229 | public void endTransact() throws SQLException { 230 | Connection con = null; 231 | final String threadId = Long.toString(Thread.currentThread().getId()); 232 | synchronized (inTransactionMap) { 233 | con = inTransactionMap.remove(threadId); 234 | } 235 | if (con != null) { 236 | try { 237 | con.commit(); 238 | } finally { 239 | con.setAutoCommit(true); 240 | freeConnection(con, false); 241 | } 242 | } 243 | } 244 | 245 | public void freeAllConnection(Connection connect) { 246 | freeConnection(connect, true); 247 | if(freeList.size() > 0) { 248 | synchronized (freeList) { 249 | while (freeList.size() > 0) { 250 | ConnectionInfo conInfo = freeList.remove(0); 251 | freeConnection(conInfo.connection, true); 252 | } 253 | } 254 | } 255 | } 256 | 257 | public void freeAllConnection() { 258 | if(freeList.size() > 0) { 259 | synchronized (freeList) { 260 | while (freeList.size() > 0) { 261 | ConnectionInfo conInfo = freeList.remove(freeList.size()-1); 262 | freeConnection(conInfo.connection, true); 263 | } 264 | } 265 | } 266 | } 267 | 268 | private void createDatabase(Statement stat, IDataBaseConf conf) throws SQLException { 269 | StringBuilder sb = new StringBuilder(128); 270 | sb.append("CREATE DATABASE IF NOT EXISTS "); 271 | sb.append(conf.getDbName()); 272 | sb.append(" DEFAULT CHARACTER SET "); 273 | sb.append(conf.getCharset()); 274 | sb.append(" DEFAULT COLLATE "); 275 | sb.append(conf.getCollation()); 276 | stat.executeUpdate(sb.toString()); 277 | } 278 | } 279 | --------------------------------------------------------------------------------