1、版本
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.fd</groupId>
<artifactId>dome</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>dome</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、定义通用entity
package com.fd.dome.base.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class BaseEntity implements Serializable {
public final static String IS_TRUE = "1";
public final static String IS_FALSE = "0";
private Long id;
private String createUser;
private Date createTime;
private String updateUser;
private Date updateTime;
private String isDel; //0 正常状态, 1 删除/隐藏状态
}
3、定义通用的dao
package com.fd.dome.base.dao;
import com.fd.dome.base.dao.provider.BaseDaoProvider;
import com.fd.dome.base.entity.BaseEntity;
import org.apache.ibatis.annotations.*;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface BaseDao<E extends BaseEntity> {
@SelectProvider(type = BaseDaoProvider.class, method = "findById")
E findById(Long id, Class clazz);
@SelectProvider(type = BaseDaoProvider.class, method = "findAll")
List<E> findAll(Class clazz);
@UpdateProvider(type = BaseDaoProvider.class, method = "save")
void save(@Param("entity") E entity, Class clazz);
@UpdateProvider(type = BaseDaoProvider.class, method = "delById")
void delById(Long id, Class clazz);
@UpdateProvider(type = BaseDaoProvider.class, method = "delByIdAndUpdateUser")
void delByIdAndUpdateUser(Long id, String updateUser, Class clazz);
@InsertProvider(type = BaseDaoProvider.class, method = "insertBatch")
void insertBatch(@Param("collection") Collection<E> entityCollection, Class clazz);
}
4、定义BaseDaoProvider用Java实现dao接口
package com.fd.dome.base.dao.provider;
import com.fd.dome.base.entity.BaseEntity;
import com.fd.dome.base.util.LocalObjectUtil;
import com.fd.dome.base.util.LocalStringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;
import java.util.*;
import static com.fd.dome.base.constants.Constants.BaseEntityConstant.*;
import static com.fd.dome.base.constants.Constants.Symbol.*;
@Slf4j
public class BaseDaoProvider<E extends BaseEntity> {
public String findById(Long id, Class clazz) {
return new SQL() {{
SELECT(LocalObjectUtil.getPropertyNameLines(clazz));
FROM(LocalStringUtil.classToLine(clazz));
WHERE("id = #{id}");
}}.toString();
}
public String findAll(Class clazz) {
return new SQL() {{
SELECT(LocalObjectUtil.getPropertyNameLines(clazz));
FROM(LocalStringUtil.classToLine(clazz));
WHERE("is_del = " + BaseEntity.IS_FALSE);
}}.toString();
}
public String save(@Param("entity") E entity, Class clazz) {
String sql;
Set<String> notNullPropertyNameSet = LocalObjectUtil.getNotNullPropertyNameSet(entity);
if (ObjectUtils.isEmpty(entity.getId())) {
sql = new SQL(){{
INSERT_INTO(LocalStringUtil.classToLine(clazz));
notNullPropertyNameSet.forEach(e -> {
if (e.equalsIgnoreCase(ID)) return;
if (e.equalsIgnoreCase(CREATE_TIME)) return;
if (e.equalsIgnoreCase(UPDATE_TIME)) return;
if (e.equalsIgnoreCase(UPDATE_USER)) return;
if (e.equalsIgnoreCase(IS_DEL)) return;
VALUES(LocalStringUtil.humpToLine(e) , "#{entity." + LocalStringUtil.toLowerCaseFirstOne(e)+ "}");
});
if (StringUtils.isNotBlank(entity.getCreateUser()))
VALUES("update_user", "#{entity.createUser}");
INTO_COLUMNS("create_time", "update_time", "is_del");
INTO_VALUES("now()", "now()", BaseEntity.IS_FALSE);
}}.toString();
} else {
sql = new SQL(){{
UPDATE(LocalStringUtil.classToLine(clazz));
notNullPropertyNameSet.forEach(e -> {
if (e.equalsIgnoreCase(ID)) return;
if (e.equalsIgnoreCase(CREATE_USER)) return;
if (e.equalsIgnoreCase(CREATE_TIME)) return;
if (e.equalsIgnoreCase(UPDATE_TIME)) return;
if (e.equalsIgnoreCase(IS_DEL)) return;
SET(LocalStringUtil.humpToLine(e) + "= #{entity." + LocalStringUtil.toLowerCaseFirstOne(e)+ "}");
});
SET("update_time = now()");
WHERE("id = #{entity.id}");
}}.toString();
}
return sql;
}
public String delById(Long id, Class clazz) {
return new SQL(){{
UPDATE(LocalStringUtil.classToLine(clazz));
SET("is_del = " + BaseEntity.IS_TRUE, "update_time = now()");
WHERE("id = #{id}");
}}.toString();
}
public String delByIdAndUpdateUser(Long id, String updateUser, Class clazz) {
return new SQL(){{
UPDATE(LocalStringUtil.classToLine(clazz));
SET("is_del = " + BaseEntity.IS_TRUE, "update_user = #{updateUser}", "update_time = now()");
WHERE("id = #{id}");
}}.toString();
}
public String insertBatch(@Param("collection") Collection<E> entityCollection, Class clazz) {
List<String> columnList = LocalObjectUtil.getPropertyNameList(clazz, new ArrayList<>());
StringBuffer columnName = new StringBuffer();
StringBuffer columnValue = new StringBuffer();
columnList.forEach(column -> {
if (column.equalsIgnoreCase(ID)) return;
if (column.equalsIgnoreCase(CREATE_TIME)) return;
if (column.equalsIgnoreCase(UPDATE_TIME)) return;
if (column.equalsIgnoreCase(UPDATE_USER)) return;
if (column.equalsIgnoreCase(IS_DEL)) return;
columnName.append(LocalStringUtil.humpToLine(column)).append(COMMA);
columnValue.append(", #{collection[__0].").append(column).append("}");
});
columnName.append(" create_time, update_time, update_user, is_del ");
columnValue.append(", now(), now(), #{collection[__0].createUser}, ").append(BaseEntity.IS_FALSE);
String valueTmp = LEFT_BRACKETS + columnValue.toString().substring(1) + RIGHT_BRACKETS;
StringBuilder value = new StringBuilder();
for (int i = 0; i < entityCollection.size(); i++) {
value.append(valueTmp.replaceAll("__0", i+""));
if (entityCollection.size() - 1 > i)
value.append(COMMA);
}
return "INSERT INTO " + LocalStringUtil.classToLine(clazz) + SPACE +
LEFT_BRACKETS + columnName.toString() + RIGHT_BRACKETS + SPACE +
" VALUES " + value;
}
}
5、工具类:
package com.fd.dome.base.constants;
import com.fd.dome.base.entity.BaseEntity;
/**
* Created by winfrd on 17-7-26.
*/
public class Constants {
public static final class Symbol {
public static final String COMMA = ",";
public static final String UNDER_LINE = "_";
public static final String MID_LINE = "-";
public static final String GREATER_THAN = ">";
public static final String DOUBLE_QUOTES = "\"";
public static final String POINT = ".";
public static final String COLON = ":";
public static final String PERCENT_SIGN = "%";
public static final String BACKSLASH = "/";
public static final String SEPARATOR = "\\|";
public static final String SEPARATOR2 = "|";
public static final String NULL_STRING = "";
public static final String SPACE = " ";
public static final String LEFT_BRACKETS = "(";
public static final String RIGHT_BRACKETS = ")";
}
public static final class Num {
public static final class IntNum {
public static final int ONE = 1;
}
}
public static final class BaseEntityConstant {
public static final String ID = "id";
public static final String CREATE_USER = "createUser";
public static final String CREATE_TIME = "createTime";
public static final String UPDATE_USER = "updateUser";
public static final String UPDATE_TIME = "updateTime";
public static final String IS_DEL = "isDel";
}
}
package com.fd.dome.base.util;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.*;
public class LocalObjectUtil {
public static void copyNonNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
}
public static String[] getNullPropertyNames (Object source) {
return getPropertyNames(true, source);
}
public static String[] getNotNullPropertyNames(Object source) {
return getPropertyNames(false, source);
}
public static String[] getPropertyNames(boolean isNull, Object source){
Set<String> emptyNames = getPropertyNameSet(isNull, source);
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
public static Set<String> getNotNullPropertyNameSet(Object source) {
return getPropertyNameSet(false, source);
}
public static Set<String> getPropertyNameSet(boolean isNull, Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<>();
for(java.beans.PropertyDescriptor pd : pds) {
if ("class".equalsIgnoreCase(pd.getName())) continue;
Object srcValue = src.getPropertyValue(pd.getName());
if (isNull) {
if (ObjectUtils.isEmpty(srcValue)) emptyNames.add(pd.getName());
} else {
if (ObjectUtils.isNotEmpty(srcValue)) emptyNames.add(pd.getName());
}
}
return emptyNames;
}
public static List<String> getPropertyNameList(Class clazz, List<String> nameList) {
return getPropertyNameList(clazz, nameList, true);
}
public static List<String> getNotSupperPropertyNameList(Class clazz, List<String> nameList) {
return getPropertyNameList(clazz, nameList, false);
}
private static List<String> getPropertyNameList(Class clazz, List<String> nameList, boolean needSuper) {
Field[] pFields = clazz.getFields();
List<Field> fieldList = Arrays.asList(pFields);
Arrays.stream(clazz.getDeclaredFields()).forEach(field -> {
if (fieldList.contains(field)) return;
nameList.add(LocalStringUtil.toLowerCaseFirstOne(field.getName()));
});
if (needSuper&& clazz.getSuperclass() != Object.class) {
getPropertyNameList(clazz.getSuperclass(), nameList);
}
return nameList;
}
public static String[] getPropertyNameLines(Class clazz) {
List<String> nameList = getPropertyNameList(clazz, new ArrayList<>());
String[] names = new String[nameList.size()];
for (int i = 0; i < nameList.size(); i++) {
names[i] = LocalStringUtil.humpToLine(nameList.get(i));
}
return names;
}
public static boolean isEmpty(Object object) {
if (object == null) {
return true;
} else if (object instanceof CharSequence) {
return ((CharSequence)object).length() == 0;
} else if (object.getClass().isArray()) {
return Array.getLength(object) == 0;
} else if (object instanceof Collection) {
return ((Collection)object).isEmpty();
} else {
return object instanceof Map ? ((Map)object).isEmpty() : false;
}
}
}
package com.fd.dome.base.util;
import com.fd.dome.base.constants.Constants;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LocalStringUtil {
private static Pattern humpPattern = Pattern.compile("[A-Z]");
private static Pattern linePattern = Pattern.compile("_(\\w)");
private static Pattern midLinePattern = Pattern.compile("-(\\w)");
/** 驼峰转下划线 */
public static String humpToLine(String str) {
Matcher matcher = humpPattern.matcher(toLowerCaseFirstOne(str));
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/** 下划线转驼峰 */
public static String lineToHump(String str) {
return toHump(str, Constants.Symbol.UNDER_LINE);
}
/** 中横线转驼峰 */
public static String midLineToHump(String str) {
return toHump(str, Constants.Symbol.MID_LINE);
}
private static String toHump(String str, String symbol) {
str = str.toLowerCase();
Matcher matcher;
if (Constants.Symbol.MID_LINE.equals(symbol)) {
matcher = midLinePattern.matcher(str);
} else {
matcher = linePattern.matcher(str);
}
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/** 对象转下划线 BaseDaoProvider => base_dao_provider*/
public static String classToLine(Class clazz) {
return humpToLine(clazz.getSimpleName());
}
/** 首字母转小写*/
public static String toLowerCaseFirstOne(String s){
if(Character.isLowerCase(s.charAt(0)))
return s;
else
return Character.toLowerCase(s.charAt(0)) + s.substring(1);
}
public static Set<String> getTargetListWithPattern(Pattern pattern , String str) {
Set<String> list = new HashSet<>();
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
list.add(matcher.group());
}
return list;
}
}
6、通用service接口与实现类
package com.fd.dome.base.service;
import com.fd.dome.base.entity.BaseEntity;
import com.fd.dome.base.vo.PageVo;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface BaseService<E extends BaseEntity> {
E findById(Long id);
List<E> findAll();
void save(E entity);
void delById(Long id);
void delByIdAndUpdateUser(Long id, String updateUser);
void insertBatch(Collection<E> entityCollection);
}
package com.fd.dome.base.service.impl;
import com.fd.dome.base.constants.Constants;
import com.fd.dome.base.dao.BaseDao;
import com.fd.dome.base.entity.BaseEntity;
import com.fd.dome.base.service.BaseService;
import com.fd.dome.base.vo.PageVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.ParameterizedType;
import java.util.*;
public abstract class BaseServiceImpl<E extends BaseEntity, DAO extends BaseDao<E>> implements BaseService<E> {
@Autowired
protected DAO dao;
@Override
public E findById(Long id) {
return dao.findById(id, getLocalClass());
}
@Override
public List<E> findAll() {
return dao.findAll(getLocalClass());
}
@Override
public void save(E entity) {
dao.save(entity, entity.getClass());
}
@Override
public void delById(Long id) {
dao.delById(id, getLocalClass());
}
@Override
public void delByIdAndUpdateUser(Long id, String updateUser) {
dao.delByIdAndUpdateUser(id, updateUser, getLocalClass());
}
@Override
public void insertBatch(Collection<E> entityCollection) {
insertBatch(entityCollection, 100);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertBatch(Collection<E> entityCollection, int size) {
int tmp = (int)Math.ceil((double)entityCollection.size()/size);
Iterator<E> iterator = entityCollection.iterator();
for (int i = 0; i < tmp; i++) {
Collection<E> collection = new ArrayList<>();
for (int j = 0; j < size; j++) {
if (iterator.hasNext())
collection.add(iterator.next());
}
dao.insertBatch(collection, getLocalClass());
}
}
private Class<E> getLocalClass() {
return (Class <E>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
}
7、springboot中的mybatis配置
mybatis:
mapper-locations: mapper/*Mapper.xml # 通用方法以外的方法实习
configuration:
map-underscore-to-camel-case: true # 驼峰
8、使用用例:
package com.fd.dome.entity;
import com.fd.dome.base.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(callSuper = true)
public class User extends BaseEntity {
private String account;
private String car;
}
package com.fd.dome.dao;
import com.fd.dome.base.dao.BaseDao;
import com.fd.dome.entity.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserDao extends BaseDao<User> {
User findByAccount(String account);
}
package com.fd.dome.service;
import com.fd.dome.base.service.BaseService;
import com.fd.dome.entity.User;
public interface UserService extends BaseService<User> {
User findBuAccount(String account);
}
package com.fd.dome.service.impl;
import com.fd.dome.base.service.impl.BaseServiceImpl;
import com.fd.dome.dao.UserDao;
import com.fd.dome.entity.User;
import com.fd.dome.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends BaseServiceImpl<User, UserDao> implements UserService {
@Override
public User findBuAccount(String account) {
return dao.findByAccount(account);
}
}
UserDaoMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fd.dome.dao.UserDao">
<select id="findByAccount" resultType="com.fd.dome.entity.User">
select * from user where account = #{account} and is_del = 0
</select>
</mapper>