jdbc 系列文章列表, 请查看目录: 《jdbc学习笔记》
熟悉Hibernate/JPA的同学知道, Hibernate/JPA对单表的增删改查是相当方便的, 直接操作java 实体即可, 这是jdbc 和 Mybatis 是所不能及的. jdbc 和 Mybatis 在操作单表的增删改错时, 依然需要手写sql, 比较繁琐. 那么我们就使用jdbc 实现一下类似于Hibernate/JPA 对单表的操作.
1. 方案设计
1.1 实体与表映射设计
对于Java实体和数据库表名映射方案, 笔者通过自定义注解类实现. 对于属性和列名, 笔者采取一一对应的关系.
@TableName: 指定Java类与数据库表表名的映射关系@AutoIncId: 指定自增id
1.2 单表增删改sql 缓存设计
如果每次查询都需要使用反射生成Sql, 那样性能会很差, 因此笔者设计了缓存.
EntityMetaDataCache: 缓存接口, 面向接口编程, 可切换多种缓存方式DefaultEntityMetaDataCache: 默认缓存, 笔者采用jvm 内存作为默认缓存.EntityMetadata: 缓存的类基本信息, 包含类基本信息: 类属性, 映射标明, 字段列表, 增删改查sql语句EntityMetadataFactory: 解析类信息
1.3 单表增删改API 设计
对于单表的操作, 笔者仅封装5个API 来演示, 读者可以自行扩展其它API:
方法签名方法描述参数说明
public static boolean save(Object entity)保存实体entity: 数据库实体 public static boolean deleteById(Class clz, Integer id)通过id 删除实体clz: 实体类型 id: 主键 public static boolean update(Object entity)更新实体entity: 实体 public static T findById(Class clz, Integer id)通过主键id查询clz: 返回类型 id: 主键 public static List queryAll(Class clz)通过主键id查询clz: 返回类型
2. 自定义注解
对单表的自动化操作需要指定实体与表的对应关系,
2.1 表名映射注解
指定Java实体与数据库表名的映射关系.属性和表字段名一一对应.
@Documented
@Target(ElementType
.TYPE
)
@Retention(RetentionPolicy
.RUNTIME
)
public @
interface TableName {
String
value();
}
2.2 主键标识注解
笔者使用的是myssql, 为了测试自动回填自增id, 所以需要定义标识自增id的注解.
@Documented
@Target(ElementType
.FIELD
)
@Retention(RetentionPolicy
.RUNTIME
)
public @
interface AutoIncId {
String
value();
}
3. 缓存设计
3.1 实体信息-EntityMetadata
public class EntityMetadata {
private Class
clz;
private String tableName
;
private List
<String> columnNames
= new ArrayList<>();
private String idColumnName
;
private String insertSql
;
private String updateSql
;
private String findByIdSql
;
private String deleteByIdSql
;
private String queryAllSql
;
}
3.2 缓存接口IEntityMetaDataCache
public interface IEntityMetaDataCache {
void setCache(EntityMetadata entityMetadata
);
EntityMetadata
getCache(Class
clz);
}
3.3 缓存默认实现-DefaultEntityMetaDataCache
public class DefaultEntityMetaDataCache implements IEntityMetaDataCache {
private Map
<Class, EntityMetadata> cacheMap
= new HashMap<>();
@Override
public void setCache(EntityMetadata entityMetadata
) {
cacheMap
.put(entityMetadata
.getClz(), entityMetadata
);
}
@Override
public EntityMetadata
getCache(Class
clz) {
return cacheMap
.get(clz
);
}
}
3.4 缓存工厂-EntityMetadataFactory
public class EntityMetadataFactory {
private static String SQL_SPACE
= " ";
public static EntityMetadata newInstance
(Class
clz){
EntityMetadata metadata
= new EntityMetadata();
Field
[] declaredFields
= clz
.getDeclaredFields();
TableName tableNameAnno
= (TableName
) clz
.getAnnotation(TableName
.class);
if (tableNameAnno
== null
) {
throw new RuntimeException(clz
.getName() + "无未使用@TableName 注解指定映射表名!");
}
metadata
.setClz(clz
);
metadata
.setTableName(tableNameAnno
.value());
for (Field declaredField
: declaredFields
) {
metadata
.getColumnNames().add(declaredField
.getName());
if (declaredField
.isAnnotationPresent(AutoIncId
.class)) {
metadata
.setIdColumnName(declaredField
.getName());
}
}
initInsertSql(metadata
);
initUpdateSql(metadata
);
initDeleteByIdSql(metadata
);
initFindByIdSql(metadata
);
initQueryAllSql(metadata
);
return metadata
;
}
private static void initQueryAllSql(EntityMetadata metadata
) {
StringBuilder sb
= new StringBuilder();
sb
.append("SELECT");
sb
.append(SQL_SPACE
);
for (String columnName
: metadata
.getColumnNames()) {
sb
.append(columnName
).append(",").append(SQL_SPACE
);
}
sb
.deleteCharAt(sb
.length() - 2);
sb
.append("FROM");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getTableName());
metadata
.setQueryAllSql(sb
.toString());
}
private static void initFindByIdSql(EntityMetadata metadata
) {
StringBuilder sb
= new StringBuilder();
sb
.append("SELECT");
sb
.append(SQL_SPACE
);
for (String columnName
: metadata
.getColumnNames()) {
sb
.append(columnName
).append(",").append(SQL_SPACE
);
}
sb
.deleteCharAt(sb
.length() - 2);
sb
.append("FROM");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getTableName());
sb
.append(SQL_SPACE
);
sb
.append("WHERE");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getIdColumnName());
sb
.append("=?");
metadata
.setFindByIdSql(sb
.toString());
}
private static void initDeleteByIdSql(EntityMetadata metadata
) {
StringBuilder sb
= new StringBuilder();
sb
.append("DELETE FROM");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getTableName());
sb
.append(SQL_SPACE
);
sb
.append("WHERE");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getIdColumnName());
sb
.append("=?");
metadata
.setDeleteByIdSql(sb
.toString());
}
private static void initUpdateSql(EntityMetadata metadata
) {
StringBuilder sb
= new StringBuilder();
sb
.append("UPDATE");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getTableName());
sb
.append(SQL_SPACE
);
sb
.append("set");
sb
.append(SQL_SPACE
);
for (String column
: metadata
.getColumnNames()) {
sb
.append(column
).append("=?, ");
}
sb
.deleteCharAt(sb
.length() - 2);
sb
.append("WHERE");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getIdColumnName());
sb
.append("=?");
metadata
.setUpdateSql(sb
.toString());
}
private static void initInsertSql(EntityMetadata metadata
) {
StringBuilder sb
= new StringBuilder();
sb
.append("INSERT INTO");
sb
.append(SQL_SPACE
);
sb
.append(metadata
.getTableName());
sb
.append("(");
for (String columnName
: metadata
.getColumnNames()) {
sb
.append(columnName
).append(", ");
}
sb
.delete(sb
.length() - 2, sb
.length());
sb
.append(")");
sb
.append(SQL_SPACE
);
sb
.append("VALUES(");
for (int i
= 0; i
< metadata
.getColumnNames().size(); i
++) {
sb
.append("?, ");
}
sb
.delete(sb
.length() - 2, sb
.length());
sb
.append(")");
metadata
.setInsertSql(sb
.toString());
}
}
4. 单表实体管理器
public class EntityManager {
private static Logger logger
= Logger
.getLogger(EntityManager
.class.toString());
private static IEntityMetaDataCache metaDataCache
= new DefaultEntityMetaDataCache();
public static boolean save(Object entity
) {
EntityMetadata entityMetadata
= getEntityMetadata(entity
.getClass());
Connection connection
= DbConnUtil
.getLocalConnection();
ResultSet callbackKeyRs
;
int cnt
= 0;
try {
PreparedStatement preparedStatement
= connection
.prepareStatement(entityMetadata
.getInsertSql(), Statement
.RETURN_GENERATED_KEYS
);
for (int i
= 1; i
<= entityMetadata
.getColumnNames().size(); i
++) {
String columnName
= entityMetadata
.getColumnNames().get(i
- 1);
Object value
= ReflectUtil
.getPropertyValue(entity
, columnName
);
preparedStatement
.setObject(i
, value
);
}
cnt
= preparedStatement
.executeUpdate();
callbackKeyRs
= preparedStatement
.getGeneratedKeys();
if (callbackKeyRs
.next()) {
int id
= callbackKeyRs
.getInt(1);
ReflectUtil
.setPropertyValue(entity
, entityMetadata
.getIdColumnName(), id
);
}
} catch (SQLException e
) {
e
.printStackTrace();
}
return cnt
> 0;
}
public static <T> boolean deleteById(Class
<T> clz
, Integer id
) {
EntityMetadata entityMetadata
= getEntityMetadata(clz
);
Connection connection
= DbConnUtil
.getLocalConnection();
int cnt
= 0;
try {
PreparedStatement preparedStatement
= connection
.prepareStatement(entityMetadata
.getDeleteByIdSql());
preparedStatement
.setInt(1, id
);
cnt
= preparedStatement
.executeUpdate();
} catch (SQLException e
) {
e
.printStackTrace();
}
return cnt
>0;
}
public static boolean update(Object entity
) {
EntityMetadata entityMetadata
= getEntityMetadata(entity
.getClass());
Connection connection
= DbConnUtil
.getLocalConnection();
int cnt
= 0;
try {
PreparedStatement preparedStatement
= connection
.prepareStatement(entityMetadata
.getUpdateSql());
for (int i
= 1; i
<= entityMetadata
.getColumnNames().size(); i
++) {
String columnName
= entityMetadata
.getColumnNames().get(i
- 1);
Object value
= ReflectUtil
.getPropertyValue(entity
, columnName
);
preparedStatement
.setObject(i
, value
);
}
Object pkValue
= ReflectUtil
.getPropertyValue(entity
, entityMetadata
.getIdColumnName());
preparedStatement
.setObject(entityMetadata
.getColumnNames().size()+1,pkValue
);
cnt
= preparedStatement
.executeUpdate();
} catch (SQLException e
) {
e
.printStackTrace();
}
return cnt
> 0;
}
public static <T> T
findById(Class
<T> clz
, Integer id
) {
EntityMetadata entityMetadata
= getEntityMetadata(clz
);
Connection connection
= DbConnUtil
.getLocalConnection();
T t
= null
;
try {
PreparedStatement preparedStatement
= connection
.prepareStatement(entityMetadata
.getFindByIdSql());
preparedStatement
.setInt(1, id
);
ResultSet resultSet
= preparedStatement
.executeQuery();
t
= ResultSetUtil
.toBean(resultSet
, clz
);
} catch (SQLException e
) {
e
.printStackTrace();
}
return t
;
}
public static <T> List
<T> queryAll(Class
<T> clz
) {
EntityMetadata entityMetadata
= getEntityMetadata(clz
);
Connection connection
= DbConnUtil
.getLocalConnection();
List
<T> list
= null
;
try {
PreparedStatement preparedStatement
= connection
.prepareStatement(entityMetadata
.getQueryAllSql());
ResultSet resultSet
= preparedStatement
.executeQuery();
list
= ResultSetUtil
.toBeans(resultSet
, clz
);
} catch (SQLException e
) {
e
.printStackTrace();
}
return list
;
}
private static EntityMetadata
getEntityMetadata(Class
clz) {
EntityMetadata entityMetadata
= metaDataCache
.getCache(clz
);
if (entityMetadata
== null
) {
entityMetadata
= EntityMetadataFactory
.newInstance(clz
);
metaDataCache
.setCache(entityMetadata
);
logger
.info("获取实体信息:-第一次解析");
}else {
logger
.info("获取实体信息:-从缓存中获取");
}
return entityMetadata
;
}
}
5. 测试用例
public class EntityManagerTest {
@Test
public void save(){
UserPO userPO
= new UserPO();
userPO
.setName("zhangsan_" + Instant
.now().getEpochSecond());
userPO
.setPassword("123456");
boolean isSuccess
= EntityManager
.save(userPO
);
Assert
.assertEquals(true, isSuccess
);
Assert
.assertNotNull(userPO
.getId());
}
@Test
public void deleteById() {
UserPO userPO
= new UserPO();
userPO
.setName("zhangsan_" + Instant
.now().getEpochSecond());
userPO
.setPassword("abcdefg");
EntityManager
.save(userPO
);
boolean isSuccess
= EntityManager
.deleteById(UserPO
.class, userPO
.getId());
Assert
.assertEquals(true, isSuccess
);
}
@Test
public void update(){
UserPO userPO
= new UserPO();
userPO
.setName("zhangsan");
userPO
.setPassword("123456");
EntityManager
.save(userPO
);
userPO
.setName("lisi");
userPO
.setPassword("123456");
EntityManager
.update(userPO
);
UserPO afterUserPO
= EntityManager
.findById(UserPO
.class, userPO
.getId());
Assert
.assertEquals("lisi", afterUserPO
.getName());
}
@Test
public void findById(){
UserPO userPO
= new UserPO();
userPO
.setName(LocalDateTime
.now().toString());
userPO
.setPassword("123456");
EntityManager
.save(userPO
);
UserPO afterUserPO
= EntityManager
.findById(UserPO
.class, userPO
.getId());
Assert
.assertNotNull(afterUserPO
);
Assert
.assertEquals(userPO
.getId(), afterUserPO
.getId());
Assert
.assertEquals(userPO
.getName(), afterUserPO
.getName());
}
@Test
public void queryAll(){
List
<UserPO> userPOS
= EntityManager
.queryAll(UserPO
.class);
Assert
.assertNotNull(userPOS
);
userPOS
.forEach(System
.out
::println
);
}
}