本文向大家介绍对JDBC简单的封装,自定义mini-mapper,希望以此加深大家对此技术的理解。
一、JDBC简介
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
Java 具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特性,是编写数据库应用程序的杰出语言。所需要的只是 Java应用程序与各种不同数据库之间进行对话的方法。
数据库是实现持久化的一种途径,而JDBC则是通向数据库的桥梁。
二、环境准备
1、引入maven依赖
2、引入配置文件jdbc.properties
jdbc.driveClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mini_mapper?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&verifyServerCertificate=false&useSSL=false&useAffectedRows=true&zeroDateTimeBehavior=convertToNull
jdbc.username=root
jdbc.password=pf1234
3、新建测试数据表
CREATE TABLE `t_user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`name` varchar(255) DEFAULT '' COMMENT '姓名',
`age` tinyint(3) unsigned DEFAULT '0' COMMENT '年龄',
`birthday` date DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、封装MiniJdbcTemplate
MiniJdbcTemplate提供对数据库表的增删改查操作
1、定义JdbcUtil
获取数据库链接,关闭数据库链接,放在JdbcUtil里维护
public final class JdbcUtil {
/**
* 数据库链接
*/
private static String url = null;
/**
* 用户名
*/
private static String username = null;
/**
* 密码
*/
private static String password = null;
static {
try (
InputStream inputStream = JdbcUtil.class.getClassLoader().getResourceAsStream("jdbc.properties")
) {
Properties properties = new Properties();
properties.load(inputStream);
String driveClassName = properties.getProperty("jdbc.driveClassName");
Class.forName(driveClassName);
url = properties.getProperty("jdbc.url");
username = properties.getProperty("jdbc.username");
password = properties.getProperty("jdbc.password");
} catch (Exception e) {
throw new RuntimeException("加载驱动异常", e);
}
}
private JdbcUtil() {
}
/**
* 获取数据库链接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
/**
* 关闭链接
*/
public static void close(Connection connection, PreparedStatement preparedStatement, ResultSet resultSet) throws SQLException {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
}
}
2、定义常量
public interface Constant {
String COMMA = ",";
String UNDERSCORE = "_";
char CHAR_UNDERSCORE = '_';
String ASTERISK = "*";
String SPACE = " ";
String QUESTION_MARK = "?";
String EQ = "=";
String LEFT_BRACKET = "(";
String RIGHT_BRACKET = ")";
}
3、定义数据转换策略接口
@FunctionalInterface
public interface RowMapper
/**
* 将结果集转为指定的Bean
*/
T mapRow(ResultSet rs) throws SQLException;
}
4、基于策略查询集合
/**
* 基于策略查询集合
*/
public static
//获取链接
Connection conn = JdbcUtil.getConnection();
//获取执行平台
PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);
//获取结果集
ResultSet rs = preparedStatement.executeQuery();
List
while (rs.next()) {
//将结果集转为指定的Bean
T bean = rowMapper.mapRow(rs);
list.add(bean);
}
//释放链接
JdbcUtil.close(conn, preparedStatement, rs);
return list;
}
Junit测试
@Test
public void testQueryRowMapper() throws SQLException {
String sql = "select * from t_user where id = ?";
List
List
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
user.setBirthday(rs.getDate("birthday"));
return user;
});
System.out.println(list);
}
结果: [User(id=1, name=张三, age=19, birthday=2003-05-02)]
5、基于反射查询集合
/**
* 基于反射查询集合
*/
public static
//获取链接
Connection conn = JdbcUtil.getConnection();
//获取执行平台
PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);
//获取结果集
ResultSet rs = preparedStatement.executeQuery();
List
while (rs.next()) {
T bean = tClass.newInstance();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 0; i < columnCount; i++) {
//获取列名
String columnName = metaData.getColumnName(i + 1);
//获取属性名
String fieldName = CommonUtil.getFieldName(columnName);
Field field = tClass.getDeclaredField(fieldName);
Object value;
//mysql依赖包不支持传入date类型解析
if (field.getType().equals(Date.class)) {
value = rs.getDate(columnName);
} else {
value = rs.getObject(columnName, field.getType());
}
field.setAccessible(true);
field.set(bean, value);
}
list.add(bean);
}
JdbcUtil.close(conn, preparedStatement, rs);
return list;
}
Junit测试
@Test
public void testQuery() throws Exception {
String sql = "select * from t_user where id = ?";
List
List
System.out.println(list);
}
结果: [User(id=1, name=张三, age=19, birthday=2003-05-02)]
6、增删改方法
/**
* 增删改操作
*/
public static int update(String sql, List
//获取链接
Connection conn = JdbcUtil.getConnection();
//获取执行平台
PreparedStatement preparedStatement = getPreparedStatement(sql, params, conn);
//执行更新(增删改)
int row = preparedStatement.executeUpdate();
//释放链接
JdbcUtil.close(conn, preparedStatement, null);
return row;
}
Junit测试
@Test
public void insert() throws SQLException {
String sql = "insert into t_user(name,age,birthday) values(?,?,?)";
List
int row = MiniJdbcTemplate.update(sql, params);
System.out.println(row);
}
结果: 1
四、定义LambdaQueryWrapper
仿照maybatis-plus,通过Lambda表达式获取数据库字段,LambdaQueryWrapper职责是构建筛选条件
1、定义支持序列化的Function
@FunctionalInterface
public interface ConditionFunction
}
2、定义Lambda解析工具类
public final class LambdaUtils {
private static final Pattern GET_PATTERN = Pattern.compile("^get[a-zA-Z].*");
private LambdaUtils() {
}
/**
* 获取字段名
*/
public static
try {
Method method = fn.getClass().getDeclaredMethod("writeReplace");
method.setAccessible(Boolean.TRUE);
SerializedLambda serializedLambda = (SerializedLambda) method.invoke(fn);
String methodName = serializedLambda.getImplMethodName();
if (GET_PATTERN.matcher(methodName).find()) {
methodName = methodName.substring(3);
}
return CommonUtil.getColumnName(methodName);
} catch (Exception e) {
throw new RuntimeException("获取属性名异常", e);
}
}
}
3、定义相关条件model
/**
* 共享查询字段
*/
@Data
public class SharedString implements Serializable {
private static final long serialVersionUID = -8841176360027140318L;
private String stringValue;
}
/**
* 查询的条件参数
*/
@Builder
@Data
public class SqlParam implements Serializable {
private static final long serialVersionUID = 3693478483795808202L;
/**
* 数据库字段
*/
private String columnName;
/**
* 属性值
*/
private Object value;
/**
* 数据库关键字
*/
private String sqlKeyword;
}
4、LambdaQueryWrapper
LambdaQueryWrapper目前就提供如下几种查询条件的构建,特别复杂的就不写了
public class LambdaQueryWrapper
private static final String LIKE = "LIKE";
private static final String NOT_LIKE = "NOT LIKE";
private static final String EQ = "=";
private static final String IN = "IN";
private static final String NOT_IN = "NOT IN";
private static final String GT = ">";
private static final String LT = "<";
/**
* 查询字段
*/
private SharedString sqlSelect = new SharedString();
/**
* 条件参数
*/
private List
public LambdaQueryWrapper
addCondition(fn, value, EQ);
return this;
}
public LambdaQueryWrapper
addCondition(fn, value, LIKE);
return this;
}
public LambdaQueryWrapper
addCondition(fn, value, NOT_LIKE);
return this;
}
public LambdaQueryWrapper
addCondition(fn, Arrays.asList(values), IN);
return this;
}
public LambdaQueryWrapper
addCondition(fn, Arrays.asList(values), NOT_IN);
return this;
}
public LambdaQueryWrapper
addCondition(fn, value, GT);
return this;
}
public LambdaQueryWrapper
addCondition(fn, value, LT);
return this;
}
public LambdaQueryWrapper
if (!CommonUtil.isEmpty(fns)) {
String stringValue = Arrays.stream(fns).map(LambdaUtils::fnToColumnName).collect(Collectors.joining(Constant.COMMA));
sqlSelect.setStringValue(stringValue);
}
return this;
}
/**
* 添加条件
*/
private void addCondition(ConditionFunction
SqlParam sqlParam = SqlParam.builder()
.columnName(LambdaUtils.fnToColumnName(fn))
.value(value)
.sqlKeyword(sqlKeyword)
.build();
sqlParamList.add(sqlParam);
}
public String getSqlSelect() {
return CommonUtil.isBank(sqlSelect.getStringValue()) ? Constant.ASTERISK : sqlSelect.getStringValue();
}
public List
return sqlParamList;
}
}
五、定义AbstractBaseMapper
有了上面的铺垫,最后就是组装sql了,AbstractBaseMapper职责是通过LambdaQueryWrapper来组装sql和参数列表,传递给MiniJdbcTemplate
1.定义TableName
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
String value();
}
2、AbstractBaseMapper
public abstract class AbstractBaseMapper
private static final String INSERT_SQL = "INSERT INTO %s(%s) values(%s)";
private static final String UPDATE_SQL = "UPDATE %s SET ";
private static final String QUERY_SQL = "SELECT %s FROM %s";
private static final String AND = "AND";
private static final String WHERE = "WHERE";
private Class
private String tableName;
public AbstractBaseMapper() {
//获取泛型的class
beanClass = (Class
//获取表名
tableName = beanClass.getAnnotation(TableName.class).value();
}
/**
* 新增
*/
public int insertSelective(T bean) throws IllegalAccessException, SQLException {
List
List
List
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(Boolean.TRUE);
Object value = field.get(bean);
if (value == null) {
continue;
}
params.add(value);
columns.add(CommonUtil.getColumnName(field.getName()));
values.add(Constant.QUESTION_MARK);
}
String sql = String.format(INSERT_SQL, tableName, String.join(Constant.COMMA, columns), String.join(Constant.COMMA, values));
return MiniJdbcTemplate.update(sql, params);
}
/**
* 修改
*/
public int updateSelective(T bean, LambdaQueryWrapper
List
List
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(Boolean.TRUE);
Object value = field.get(bean);
if (value == null) {
continue;
}
params.add(value);
setParams.add(CommonUtil.getColumnName(field.getName()) + Constant.EQ + Constant.QUESTION_MARK);
}
StringBuilder condition = new StringBuilder();
handleSqlParam(wrapper, condition, params);
String sql = String.format(UPDATE_SQL, tableName) + String.join(Constant.COMMA, setParams) +
condition.toString();
return MiniJdbcTemplate.update(sql, params);
}
/**
* 查询
*/
public List
List
StringBuilder condition = new StringBuilder();
handleSqlParam(wrapper, condition, params);
String sql = String.format(QUERY_SQL, wrapper.getSqlSelect(), tableName) + condition.toString();
return MiniJdbcTemplate.queryForList(sql, params, beanClass);
}
/**
* 处理条件
*/
private void handleSqlParam(LambdaQueryWrapper
List
if (CommonUtil.isNotEmpty(sqlParamList)) {
condition.append(Constant.SPACE).append(WHERE).append(Constant.SPACE);
for (int i = 0; i < sqlParamList.size(); i++) {
SqlParam sqlParam = sqlParamList.get(i);
if (i > 0) {
condition.append(Constant.SPACE).append(AND).append(Constant.SPACE);
}
StringBuilder value = new StringBuilder();
Object paramValue = sqlParam.getValue();
if (paramValue instanceof List) {
value.append(Constant.LEFT_BRACKET);
List
for (int j = 0; j < valueList.size(); j++) {
Object obj = valueList.get(j);
params.add(obj);
if (j > 0) {
value.append(Constant.COMMA);
}
value.append(Constant.QUESTION_MARK);
}
value.append(Constant.RIGHT_BRACKET);
} else {
value.append(Constant.QUESTION_MARK);
params.add(paramValue);
}
condition.append(sqlParam.getColumnName()).append(Constant.SPACE).append(sqlParam.getSqlKeyword()).append(Constant.SPACE).append(value.toString());
}
}
}
}
3、Junit测试
@TableName("t_user")
@Data
public class User {
/**
* 主键
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 生日
*/
private Date birthday;
}
public class UserMapper extends AbstractBaseMapper
}
测试insertSelective
@Test
public void insertTest() throws SQLException, IllegalAccessException {
UserMapper userMapper = new UserMapper();
User user = new User();
user.setName("王五");
user.setAge(0);
user.setBirthday(new Date());
int row = userMapper.insertSelective(user);
System.out.println(row);
}
结果: 1
测试updateSelective
@Test
public void updateTest() throws SQLException, IllegalAccessException {
UserMapper userMapper = new UserMapper();
User user = new User();
user.setAge(12);
int row = userMapper.updateSelective(user, new LambdaQueryWrapper
System.out.println(row);
}
结果: 1
测试查询
@Test
public void queryTest() throws Exception {
UserMapper userMapper = new UserMapper();
List
.select(User::getId, User::getName, User::getAge)
.in(User::getId, 3, 4)
.gt(User::getAge,1)
.eq(User::getName, "王五"));
System.out.println(userList);
}
结果: [User(id=3, name=王五, age=12, birthday=null)]
六、总结
大家在实际开发中还是使用业界成熟的框架,不要自己手动实现,mini-mapper只是实现了一些最基本的功能,代码实现还存在瑕疵,目的是加深技术的理解,起一个抛砖引玉的作用。