当我们学习了mybatis后,我们在感叹mybatis的强大的同时,可能也会为重复的sql而感到厌烦,于是有了MybatisPlus 与 TKMybatis,对于还没用过的可能还不习惯,于是便自己想整一个简单的mapper增强,里面放置一些通用的方法,基于MybatisPlus的一些启发来实现,首先不管这些方法在mapper的具体实现,先考虑将这些通用方法在service与mapper层提取出来
我们首先定义BaseService与BaseMpper接口:
/**
* service层公用方法接口
* @param <T>
*/
public interface BaseService<T> {
T getById(int id);
List<T> getByCondition(Map paramsMap);
List<T> getByCondition(T model);
Long insert(T model);
Long updateById(T model);
Long deleteById(int id);
Boolean existById(int id);
}
/**
* 基础baseMapper,提取mapper公用的方法
*
* @param <T>
*/
public interface BaseMapper<T> {
T getById(@Param("id") int id);
List<T> getByCondition(Map paramsMap);
List<T> getByCondition(T model);
Long insert(T model);
Long updateById(T model);
Long deleteById(@Param("id") int id);
Boolean existById(@Param("id") int id);
}
这里mapper层的方法肯定是用注解的方式去解析sql,我们先不管 mapper层方法如何实现,我们来看下如此定义,我们去使用有什么弊端,在我们具体的业务模块中,比如说用户模块中,我们需要分别写接口UserService与UserMapper去继承上面的接口以使用他们的方法,然后写UserService的实现类UserServiceImpl,那我们在UserServiceImpl中反而要调用UserMapper对方法一一实现,显得繁琐,那我们能不能将UserServiceImpl中的具体实现提取出来呢?我们定义一个抽象类BaseServiceImpl实现接口BaseService,使得所有的ServiceImpl都继承它,如下:
/**
* 定义公用方法的的实现类,业务层只需继承就可以调用
* @param <T> 实体类泛型
* @param <M> Mapper泛型
*/
public abstract class BaseServiceImpl<T,M extends BaseMapper<T>> implements BaseService<T> {
@Autowired
private M m;
@Override
public List<T> getByCondition(T model){return m.getByCondition(model);}
@Override
public List<T> getByCondition(Map model){return m.getByCondition(model);}
@Override
public T getById(int id){ return m.getById(id);}
@Override
public Long insert(T model){ return m.insert(model);}
@Override
public Long updateById(T model){return m.updateById(model);}
@Override
public Long deleteById(int id){return m.deleteById(id);}
@Override
public Boolean existById(int id){return m.existById(id);}
}
注意类上面的泛型,我们将Mapper以泛型的形式传递进来,然后通过@Autowrid注解从spring容器注入,在这里做这些方法在业务层的具体实现,那我们在具体模块中就只需要继承就ok了,如下:
/**
* 此处只需要定义mapper层和service层 方法名 不同的方法
* 同名的方法抽象到 CommonUserMethodInterface 接口
*/
public interface UserMapper extends BaseMapper<User>{
}
-----------------------------------------------------------------------------------
/**
* 此处只需要定义service层方法名 和 mapper不同的方法
* 同名的方法抽象到 CommonUserMethodInterface 接口
*/
public interface UserService extends BaseService<User>{
}
------------------------------------------------------------------------------------
@Service
public class UserServiceImpl extends BaseServiceImpl<User,UserMapper> implements UserService{
}
然后我们在控制层就可以调用到我们的提取到的公用方法了,省掉了我们以前我们在每个模块的业务层去实现这些方法,然后我们来看如何去加载我们的BaseMapper,mybatis提供了一个 @Lang 的注解,允许我们去自定义解析sql的驱动,如下,我们需要定义我们自己的sql驱动
/**
* 基础baseMapper,提取mapper公用的方法
* 参数为实体类或者map的不能用 @Param 注解
* @param <T>
*/
public interface BaseMapper<T> {
/**
* 根据id查询
* @param id
* @return
*/
@Lang(BaseMapperDriver.class)
@Select("SELECT ${columns} FROM ${table} where ${id} = #{id}")
T getById(@Param("id") int id);
/**
* 条件查询,参数为map,
* 注意: 不能使用 @Param 注解,key的值必须为实体类的字段名
* @return
*/
@Lang(BaseMapperDriver.class)
@Select("SELECT ${columns} FROM ${table} where 1=1 ${conditions}")
List<T> getByCondition(Map paramsMap);
/**
* 优先使用此方法做查询
* 条件查询,参数为实体类,不能使用 @Param 注解
* @return
*/
@Lang(BaseMapperDriver.class)
@Select("SELECT * FROM ${table} where 1=1 ${conditions} ")
List<T> getByCondition(T model);
/**
* 插入
* @param model
* @return
*/
@Lang(BaseMapperDriver.class)
@Insert("INSERT INTO ${table} ${keys} values ${values}")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
Long insert(T model);
/**
* 根据id 修改
* @param model
* @return
*/
@Lang(BaseMapperDriver.class)
@Update("UPDATE ${table} ${sets} WHERE ${id}=#{id}")
Long updateById(T model);
/**
* 删除
* @param id
* @return
*/
@Lang(BaseMapperDriver.class)
@Delete("DELETE FROM ${table} WHERE ${id}=#{id}")
Long deleteById(@Param("id") int id);
/**
* 判断是否存在
* @param id
* @return
*/
@Lang(BaseMapperDriver.class)
@Select("SELECT COUNT(1) FROM ${table} WHERE ${id}=#{id}")
Boolean existById(@Param("id") int id);
}
在定义驱动之前,我们需要重写mybatis的一些配置类:
将配置类注入工厂
@Configuration
public class DataConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Bean
public SqlSessionFactoryBean configSqlSessionFactoryBean(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources(mapperLocations));// 设置mapper文件扫描路径
bean.setConfiguration(new MybatisConfig());
return bean;
}
}
在配置类的构造方法中注入 MybatisMapperRegistry
/**
* 重写mybatis的 Configuration
*/
public class MybatisConfig extends Configuration {
protected final MapperRegistry mapperRegistry;
public MybatisConfig(){
super();
this.mapperRegistry = new MybatisMapperRegistry(this);
this.mapUnderscoreToCamelCase = true;
}
@Override
public MapperRegistry getMapperRegistry() {
return this.mapperRegistry;
}
@Override
public void addMappers(String packageName, Class<?> superType) {
this.mapperRegistry.addMappers(packageName, superType);
}
@Override
public void addMappers(String packageName) {
this.mapperRegistry.addMappers(packageName);
}
@Override
public <T> void addMapper(Class<T> type) {
this.mapperRegistry.addMapper(type);
}
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
@Override
public boolean hasMapper(Class<?> type) {
return this.mapperRegistry.hasMapper(type);
}
}
在MybatisMapperRegistry中主要是在addMapper方法中捕获当前的mapper类,在我们自定义sql驱动需要用到
/**
* 自定义mapperRegistry
*/
public class MybatisMapperRegistry extends MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
private Configuration config;
private static Class<?> currentMapper;
public MybatisMapperRegistry(Configuration config) {
super(config);
this.config = config;
}
public static Class<?> getCurrentMapper() {
return currentMapper;
}
@Override
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
currentMapper = type;
parser.parse();
currentMapper=null;
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
@Override
public <T> boolean hasMapper(Class<T> type) {
return this.knownMappers.containsKey(type);
}
@Override
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(this.knownMappers.keySet());
}
@Override
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
Iterator var5 = mapperSet.iterator();
while(var5.hasNext()) {
Class<?> mapperClass = (Class)var5.next();
this.addMapper(mapperClass);
}
}
@Override
public void addMappers(String packageName) {
this.addMappers(packageName, Object.class);
}
}
然后我们去看如何自定义sql解析器
自定义类 继承XMLLanguageDriver 实现LanguageDriver , 在LanguageDriver中有三个方法,在XMLLanguageDriver中对三个方法做了简单的实现
1. ParameterHandler createParameterHandler(MappedStatement var1, Object var2, BoundSql var3);
该方法用于mybatis参数填充处理,在sql执行之前会被调用
2.SqlSource createSqlSource(Configuration var1, XNode var2, Class<?> var3);
该方法用于解析xml中的sql
3.SqlSource createSqlSource(Configuration var1, String var2, Class<?> var3);
该方法用于解析注解中的sql
我们需要重写的就是方法3,基本原理就是获取当前mapper接口的泛型,这个泛型一般都为实体类,我们在这个实体类上添加注解,标记实体类与数据库中表的对应,我添加的注解如下:
/**
* 注解到实体类属性上,标记属性对应的列名
* 只有 有此注解的才会被解析进sql
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name() default "";
}
------------------------------------------------------
/**
* 指定主键
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
String name() default "";
}
----------------------------------------------------------
/**
* 查询时默认查询所有注解有 @column 的属性对应的字段
* 注解到实体类属性上,标记查询时不被查询的属性
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoredColumn {
}
---------------------------------------------------------
/**
* 指定表名
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String name();
}
-----------------------------------------------------------
/**
* 是否使用父类属性
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseParent {
}
重写方法如下:
我们对获取到的泛型,实际就是实体类,对它的属性进行遍历,通过属性上的注解获取属性在数据库对应的字段,然后对sql进行拼接替换
/**
* 自定义 基础mapper查询驱动
*
*/
public class BaseMapperDriver extends XMLLanguageDriver implements LanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
/* 对sql进行解析处理 */
BaseMapperDriverResolver baseMapperDriverResolver = new BaseMapperDriverResolver();
script = baseMapperDriverResolver.createSqlSource(configuration,script);
return super.createSqlSource(configuration, script , parameterType);
}
}
--------------------------------------------------------------------------------------
public class BaseMapperDriverResolver extends DriverResolver {
public String createSqlSource(Configuration configuration, String script){
//获取当前mapper
Class<?> mapperClass = null;
if(configuration instanceof MybatisConfig){
mapperClass = MybatisMapperRegistry.getCurrentMapper();
if(mapperClass == null){
throw new BaseException("解析SQL出错");
}
}
//处理SQL
if(mapperClass!=null) {
/* 获取当前mapper接口上的泛型,这个泛型实际上对应着实体类 */
Class<?> generics = getMapperGenerics(mapperClass);
String newScript = super.commonCreateSqlSource(generics, script);
StringBuilder sb = new StringBuilder( "<script>");
sb.append(newScript);
sb.append("</script>");
script = sb.toString();
}
return script;
}
}
public abstract class DriverResolver implements BaseResolverInterface {
static final String tablePattern = "\\$\\{table\\}";
static final String idPattern = "\\$\\{id\\}";
static final String columnsPattern = "\\$\\{columns\\}";
static final String keysPattern = "\\$\\{keys\\}";
static final String valuesPattern = "\\$\\{values\\}";
static final String setsPattern = "\\$\\{sets\\}";
static final String conditionPattern = "\\$\\{conditions\\}";
/**
* 判断是否是select方法
* @param script
* @return
*/
public boolean isSelect(String script){
Matcher matcher = Pattern.compile("[selectSELECT]{6}").matcher(script);
/* 判断是否是select方法 */
if(matcher.find()){
return true;
} else{
return false;
}
}
/**
* 判断是否是插入语句
* @param script
* @return
*/
public boolean isInsert(String script){
Matcher matcher = Pattern.compile("[insertINSERT]{6}").matcher(script);
/* 判断是否是insert方法 */
if(matcher.find()){
return true;
} else{
return false;
}
}
/**
* 判断是否更新语句
* @param script
* @return
*/
public boolean isUpdate(String script){
Matcher matcher = Pattern.compile("[updateUPDATE]{6}").matcher(script);
/* 判断是否是update方法 */
if(matcher.find()){
return true;
} else{
return false;
}
}
/**
* 判断是否是删除语句
* @param script
* @return
*/
public boolean isDelete(String script){
Matcher matcher = Pattern.compile("[deleteDELETE]{6}").matcher(script);
/* 判断是否是delete方法 */
if(matcher.find()){
return true;
} else{
return false;
}
}
/**
* 判断sql是否含有 ${table}
* @param script
* @return
*/
public boolean hasTable(String script){
Matcher matcher = Pattern.compile(tablePattern).matcher(script);
return matcher.find();
}
/**
* 判断sql是否含有 ${id}
* @param script
* @return
*/
public boolean hasId(String script){
Matcher matcher = Pattern.compile(idPattern).matcher(script);
return matcher.find();
}
/**
* 判断sql是否含有 ${columns}
* @param script
* @return
*/
public boolean hasColumns(String script){
Matcher matcher = Pattern.compile(columnsPattern).matcher(script);
return matcher.find();
}
/**
* 判断sql是否含有 ${keys}
* @param script
* @return
*/
public boolean hasKeys(String script){
Matcher matcher = Pattern.compile(keysPattern).matcher(script);
return matcher.find();
}
/**
* 判断sql是否含有 ${values}
* @param script
* @return
*/
public boolean hasValues(String script){
Matcher matcher = Pattern.compile(valuesPattern).matcher(script);
return matcher.find();
}
/**
* 判断sql是否含有 ${sets}
* @param script
* @return
*/
public boolean hasSets(String script){
Matcher matcher = Pattern.compile(setsPattern).matcher(script);
return matcher.find();
}
/**
* 判断是否是条件查询
* @param script
* @return
*/
public boolean hasConditions(String script){
Matcher matcher = Pattern.compile(conditionPattern).matcher(script);
return matcher.find();
}
/**
* 获取mapper接口的泛型
* @param mapperClass
* @return
*/
public Class<?> getMapperGenerics(Class<?> mapperClass){
/*获取mapperClass接口的类型*/
Type[] types = mapperClass.getGenericInterfaces();
ParameterizedType parameterizedType = (ParameterizedType)types[0];
Type[] types1 = parameterizedType.getActualTypeArguments();
return (Class<?>)types1[0];
/* 多个泛型需要遍历 */
/*Class<?>[] classes = new Class[types.length];
for(Type type:types){
ParameterizedType parameterizedType = (ParameterizedType)type;
Type[] types1 = parameterizedType.getActualTypeArguments();
classes[0] = (Class<?>) types1[0];
}
return classes;*/
}
/**
* 获取实体类所有的属性
* @param generics
* @return
*/
public List<Field> getFieldList(Class<?> generics){
/* 获取所有的字段 */
//获取所有属性的合集
List<Field> fieldList = new ArrayList();
/* 判断是否有该注解 */
if(generics.isAnnotationPresent(UseParent.class)){
/* 添加父类的所有字段 */
fieldList.addAll(Arrays.asList(generics.getSuperclass().getDeclaredFields()));
}
/* 添加本类所有字段 */
fieldList.addAll(Arrays.asList(generics.getDeclaredFields()));
return fieldList;
}
/**
* 替换insert与update的列名
* 只会拼接有 @column 注解的字段
* @param field
* @return
*/
@Override
public String setKeys(Field field){
/* 获取实体类的字段名 */
String __field = field.getName();
String __column;
if (field.isAnnotationPresent(Column.class)) {
/* 获取注解中表的列名 */
__column = field.getAnnotation(Column.class).name();
/* 若注解中的值为默认值 "" ,则使用字段名进行转变 */
if ("".equals(__column)) {
__column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, __field);
}
String keysTemp = "<if test=\"__field != null\"> __column, </if>";
//拼接
return keysTemp.replaceAll("__field", __field).replaceAll("__column", __column);
}
return "";
}
/**
* 替换insert与update的实体类字段, keys的value
* 只会拼接有 @column 注解的字段
* @param field
* @return
*/
@Override
public String setValues(Field field){
if (field.isAnnotationPresent(Column.class)) {
String valuesTemp = "<if test=\"__field != null\"> #{__field} , </if>";
return valuesTemp.replaceAll("__field", field.getName());
}
return "";
}
/**
* 替换update的 set集合
* 只会拼接有 @column 注解的字段
* @param field
* @return
*/
@Override
public String setSets(Field field){
/* 获取实体类的字段名 */
String __field = field.getName();
String __column;
String temp = "";
if(field.isAnnotationPresent(Column.class)) {
/* 获取注解中表的列名 */
__column = field.getAnnotation(Column.class).name();
/* 若注解中的值为默认值 "" ,则使用字段名进行转变 */
if ("".equals(__column)) {
__column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, __field);
}
temp = "<if test=\"__field != null\"> __column=#{__field} </if>";
//拼接
temp.replaceAll("__field", __field).replaceAll("__column", __column);
}
return temp;
}
/**
* 设置需要查询的列名
* @param field
* @return
*/
@Override
public String setColumns(Field field) {
String temp = "";
boolean ignoredColumn = false;
/* 获取实体类的字段名 */
String __field = field.getName();
String __column = "";
if (field.isAnnotationPresent(Column.class)) {
/*有此注解则被忽略*/
if (field.isAnnotationPresent(IgnoredColumn.class)) {
ignoredColumn = true;
}
/* 获取注解中表的列名 */
__column = field.getAnnotation(Column.class).name();
} else if(field.isAnnotationPresent(Id.class)){
/*有此注解则被忽略*/
if (field.isAnnotationPresent(IgnoredColumn.class)) {
ignoredColumn = true;
}
/* 获取注解中表的列名 */
__column = field.getAnnotation(Id.class).name();
} else {
ignoredColumn = true;
}
if(!ignoredColumn){
/* 若注解中的值为默认值 "" ,则使用字段名进行转变 */
if ("".equals(__column)) {
__column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, __field);
}
temp = " __column __field , ";
temp = temp.replaceAll("__column", __column).replaceAll("__field",__field);
}
return temp;
}
/**
* 替换表名
* @param generics
* @return
*/
@Override
public String setTable(Class<?> generics){
/* 若未添加注解,直接返回 */
if(!generics.isAnnotationPresent(Table.class)){
throw new BaseException("类:"+generics.getName()+"未添加注解 " + Table.class.getName());
}
/* 获取注解上的表名 */
String tableName = generics.getAnnotation(Table.class).name();
/* 若table注解没有传值,使用类名进行变换 */
if("".equals(tableName)){
tableName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, generics.getName());
}
/* 替换表名 */
return tableName;
}
/**
* 替换主键
* @param field
* @return
*/
@Override
public String setId(Field field){
String __id = "";
if (field.isAnnotationPresent(Id.class)) {
/* 获取注解中表的列名 */
__id = field.getAnnotation(Id.class).name();
/* 若注解中的值为默认值 "" ,则使用字段名进行转变 */
if("".equals( __id )){
__id = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName());
}
}
return __id;
}
/**
* 拼接条件查询动态sql
* @param field
* @return
*/
@Override
public String setConditions(Field field){
boolean isPass = false;
String temp = "";
/* 获取实体类的字段名 */
String __field = field.getName();
String __column = "";
if (field.isAnnotationPresent(Id.class)) {
/* 获取注解中表的列名 */
__column = field.getAnnotation(Id.class).name();
} else if(field.isAnnotationPresent(Column.class)){
/* 获取注解中表的列名 */
__column = field.getAnnotation(Column.class).name();
} else {
/* 没有注解标记的跳过 */
isPass = true;
}
if(!isPass){
/* 若注解中的值为默认值 "" ,则使用字段名进行转变 */
if("".equals(__column)){
__column = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, __field);
}
temp = "<if test=\"__field != null\"> and __column=#{__field} </if>";
//拼接
temp = temp.replaceAll("__field", __field).replaceAll("__column", __column);
}
return temp;
}
/**
* 通用替换拼接sql
* @param generics
* @param script
* @return
*/
public String commonCreateSqlSource(Class<?> generics,String script) {
StringBuilder id = new StringBuilder();
StringBuilder columns = new StringBuilder();
StringBuilder keys = new StringBuilder();
StringBuilder values = new StringBuilder();
StringBuilder sets = new StringBuilder();
StringBuilder conditions = new StringBuilder();
List<Field> fieldList = getFieldList(generics);
//遍历所有的属性
for (Field field : fieldList) {
if(hasId(script)){
/* 获取id */
id.append(setId(field));
}
if(hasColumns(script)){
/* 拼接查询的列名 */
columns.append(setColumns(field));
}
if(hasKeys(script)){
/* 拼接insert和update的keys */
keys.append(setKeys(field));
}
if(hasValues(script)){
/* 拼接insert和update的values */
values.append(setValues(field));
}
if(hasSets(script)){
/* 拼接update的sets */
sets.append(setSets(field));
}
if (hasConditions(script)){
conditions.append(setConditions(field));
}
}
/* 判断是否需要替换 ${table} 表名不需要遍历 */
if(hasTable(script)){
String table = setTable(generics);
script = script.replaceAll(tablePattern,table);
}
/* 判断是否需要替换 ${id} */
if (hasId(script)){
script = script.replaceAll(idPattern,id.toString());
}
/* 判断是否需要替换 ${columns} */
if (hasColumns(script)){
StringBuilder sb = new StringBuilder("<trim suffixOverrides=\",\"> ");
sb.append(columns);
sb.append("</trim> ");
script = script.replaceAll(columnsPattern,sb.toString());
}
/* 判断是否需要替换 ${keys} */
if (hasKeys(script)){
StringBuilder sb = new StringBuilder("<trim suffixOverrides=\",\"> ");
sb.append(keys);
sb.append("</trim> ");
script = script.replaceAll(keysPattern,sb.toString());
}
/* 判断是否需要替换 ${values} */
if (hasValues(script)){
StringBuilder sb = new StringBuilder("<trim suffixOverrides=\",\"> ");
sb.append(values);
sb.append("</trim> ");
script = script.replaceAll(valuesPattern,sb.toString());
}
/* 判断是否需要替换 ${sets} */
if (hasSets(script)){
StringBuilder sb = new StringBuilder("<set> <trim suffixOverrides=\",\"> ");
sb.append(sets);
sb.append("</trim> </set>");
script = script.replaceAll(setsPattern,sb.toString());
}
/* 判断是否需要添加查询条件 */
if (hasConditions(script)){
StringBuilder sb = new StringBuilder();
sb.append(conditions);
script = script.replaceAll(conditionPattern,sb.toString());
}
return script;
}
}
public interface BaseResolverInterface {
String setTable(Class<?> entityClazz);
String setId(Field field);
String setColumns(Field field);
String setKeys(Field field);
String setValues(Field field);
String setSets(Field field);
String setConditions(Field field);
}
gitee地址:https://gitee.com/dchenleilei/springboot/tree/master
文章浏览阅读1.4k次。跟着视频里点击操作,但是发现自己的就是不行,指定失败时候出现的是以下指定网络框????解决办法????是没有正确选中铜皮导致的。选中功能然后把鼠标放在画好的动态铜皮上 再点一下再点击要指定的网络,就可以了。..._allegro无法设置网络
文章浏览阅读8.1k次。http://idea.iteblog.com/_idea激活网址
文章浏览阅读294次。作者 | 杏花编辑 | 琰琰今年4月,国际计算语言学协会(ACL)提出滚动审稿机制(ACL Rolling Review,ARR),以提高ACL系列会议的审稿效率和质量,并优化当前ACL会..._会议的action editor是什么意思?
文章浏览阅读893次。Bootstrap首先说 Bootstrap,估计你也猜到会先说或者一定会有这个( 呵呵了 ),这是说明它的强大之处,拥有框架一壁江山的势气。自己刚入道的时候本着代码任何一个字母都得自己敲出来挡我者废的决心,来让自己成长。结果受到周围各 种基友的引诱开始了 Bootstrap 旅程。本人虽然是个设计+前端的万里有一的人才,但是老天只让我会用 PS 和各种设计工具却不给我跟设计妹子一样的审美,所以这也是我最初选择 Bootstrap 的原因之一,它让我做出来的东西好歹能在妹子面前装个逼,不过时间长了难免觉_html5 系统框架
文章浏览阅读1.3k次。安全生产模拟考试一点通:道路运输企业安全生产管理人员考试参考答案及道路运输企业安全生产管理人员考试试题解析是安全生产模拟考试一点通题库老师及道路运输企业安全生产管理人员操作证已考过的学员汇总,相对有效帮助道路运输企业安全生产管理人员新版试题学员顺利通过考试。_道路运输危险货物车辆标志分为( )。 a. 标志线和标志牌 b. 标志牌和标志灯 c. 标
文章浏览阅读571次。入侵检测和预防系统(IDPS)软件市场的企业竞争态势 该报告涉及的主要国际市场参与者有McAfee、Trend Micro、Darktrace、Cisco、AT&T Cybersecurity、Palo Alto Networks、NSFocus、Blumira、GFI Software、Vectra AI、Splunk Technology、Check Point、ExtraHop、FireEye、Fortinet、Juniper Networks、OSSEC、Snort等。这..._国产化入侵检测系统调研
文章浏览阅读1.1k次。Node.js进入官网,如果 Window 就下载 .msi ,Mac 下载 .pkg :https://nodejs.org/zh-cn/别着急安装最新版,先看看同事都用的是什么版本,如果同事都在用 12.x ,建议也装 12.x ,如果你装 16.x ,大概率安装依赖的时候会报错。推荐使用 nvm 管理 Node 版本,不过本人是 Window 环境,就不装了。然后确认你下载的是 LTS 版本,不要装奇数版或者已经不在维护的版本,这点在 Node.js 官网也有说明。https://no_@antfu/eslint-config
文章浏览阅读95次。HTML提升1一、无序列表二、有序列表三、图片标签img路径四、超链接标签五、水平线六、表格七、图层八、行内布局一、无序列表可以用来制作一个项目符号的效果<ul> <li>C语言</li> <li>C++</li></ul>可以在ul标签中通过设置type属性来设置符号的种类,符号种类有:disc(黑点)(默认)circle(圆圈)square(方块)none(无符号)如书写:<ul type="
文章浏览阅读803次,点赞24次,收藏22次。注意 卷帘元素为map子集">
文章浏览阅读497次。_uboot.bin 修改工具
文章浏览阅读1.5k次,点赞3次,收藏23次。预定义变量是在MATLAB工作空间中驻留,由系统本身定义的变量。ans是默认赋值变量i 和 j 代表虚数单位pi代表圆周率NaN代表非数。_matlab向量组等价
文章浏览阅读1.7w次,点赞13次,收藏88次。完成了springboot+vue+onlyoffice的集成,实现用户上传文件,编辑文件的基本操作。后续将完成协作编辑,版本管理,文件加密,解密打开等相关操作。文件界面实例图:1、部署onlyoffice的dockerdocker run -i -t -d --name onlyoffice --privileged -p 9999:80 -p 5432:5432 --restart=always -v /e/publish/onlyoffice/DocumentServer/logs:/_onlyoffice/document-editor-vue 使用