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
);
    }
}