引文

  本文主要介绍如何使用mybatis插件实现拦截数据库操作并根据不同需求进行数据对比分析,主要适用于系统中需要对数据操作进行记录、在更新数据时准确记录更新字段

核心:mybatis插件(拦截器)、mybatis-Plus实体规范、数据对比

1、相关技术简介

mybatis插件

  mybatis插件实际上就是官方针对4层数据操作处理预留的拦截器,使用者可以根据不同的需求进行操作拦截并处理。这边笔者不做详细描述,详细介绍请到官网了解,这里笔者就复用官网介绍。

插件(plugins)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

 // ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
private Properties properties = new Properties();
public Object intercept(Invocation invocation) throws Throwable {
// implement pre processing if need
Object returnObject = invocation.proceed();
// implement post processing if need
return returnObject;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
<!-- mybatis-config.xml -->
<plugins>
<plugin interceptor="org.mybatis.example.ExamplePlugin">
<property name="someProperty" value="100"/>
</plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象。

提示 覆盖配置类

除了用插件来修改 MyBatis 核心行为之外,还可以通过完全覆盖配置类来达到目的。只需继承后覆盖其中的每个方法,再把它传递到 SqlSessionFactoryBuilder.build(myConfig) 方法即可。再次重申,这可能会严重影响 MyBatis 的行为,务请慎之又慎。

重点讲下4层处理,MyBatis两级缓存就是在其中两层中实现

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

    • 所有数据库操作到达底层后都由该执行器进行任务分发,主要有update(插入、更新、删除),query(查询),提交,回滚,关闭链接等
  • ParameterHandler (getParameterObject, setParameters) 
    • 参数处理器(获取参数,设置参数)
  • ResultSetHandler (handleResultSets, handleOutputParameters) 
    • 结果集处理器(结果集,输出参数)
  • StatementHandler (prepare, parameterize, batch, update, query) 
    • 声明处理器、准备链接jdbc前处理,prepare(预处理):生成sql语句,准备链接数据库进行操作

以上4层执行顺序为顺序执行

  • Executor是 Mybatis的内部执行器,它负责调用StatementHandler操作数据库,并把结果集通过 ResultSetHandler进行自动映射,另外,他还处理了二级缓存的操作。从这里可以看出,我们也是可以通过插件来实现自定义的二级缓存的。
  • ParameterHandler是Mybatis实现Sql入参设置的对象。插件可以改变我们Sql的参数默认设置。
  • ResultSetHandler是Mybatis把ResultSet集合映射成POJO的接口对象。我们可以定义插件对Mybatis的结果集自动映射进行修改。
  • StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里,我们可以使用插件来实现对一级缓存的操作(禁用等等)。

MyBatis-Plus:

  MyBatis增强器,主要规范了数据实体,在底层实现了简单的增删查改,使用者不再需要开发基础操作接口,小编认为是最强大、最方便易用的,没有之一,不接受任何反驳。详细介绍请看官网

数据实体的规范让底层操作更加便捷,本例主要实体规范中的表名以及主键获取,下面上实体规范demo

 @Data
@TableName("tb_demo")
@EqualsAndHashCode(callSuper = true)
public class Demo extends Model<Demo> {
private static final long serialVersionUID = 1L; /**
*
*/
@TableId
private Integer id;
/**
* 名称
*/
private String name; }
 

2、实现

本文所要讲述的就是在第一级(Executor)进行拦截并实现数据对比记录。
本例为公共模块实现,然后在其它模块中依赖此公共模块,根据每个模块不同的需求自定义实现不同的处理。
结构目录

一、实现拦截器

DataUpdateInterceptor,根据官网demo实现拦截器,在拦截器中根据增、删、改操作去调用各个模块中自定义实现的处理方法来达到不同的操作处理。

 package com.erp4cloud.rerp.common.data.log;

 import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.scheduling.annotation.Async; import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Properties; /**
* 数据更新拦截器
*
* @author Tophua
* @date 2019/8/2
*/
@AllArgsConstructor
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class DataUpdateInterceptor implements Interceptor { private final DataSource dataSource;
private final DataLogHandler dataLogHandler; @Override
public Object intercept(Invocation invocation) {
Object result = null;
try {
this.dealData(invocation);
result = invocation.proceed();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return result;
} @Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
} @Override
public void setProperties(Properties properties) { } /**
* 对数据库操作传入参数进行处理
*
* @param invocation
* @return void
* @author Tophua
* @date 2019/8/3
*/
public void dealData(Invocation invocation) {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
// 参数
Object et = args[1];
if (et instanceof Model) {
this.doLog(mappedStatement, et);
} else if (et instanceof Map) {
String key = "et";
String listKey = "collection";
if (((Map) et).containsKey(key) && ((Map) et).get(key) instanceof Model) {
this.doLog(mappedStatement, ((Map) et).get(key));
} else if (((Map) et).containsKey(listKey) && ((Map) et).get(listKey) instanceof Collection) {
List<Object> list = (List<Object>) ((Map) et).get(listKey);
for (Object obj : list) {
if (obj instanceof Model) {
this.doLog(mappedStatement, obj);
}
}
}
}
} /**
* 根据不同参数及操作进行不同的日志记录
*
* @param mappedStatement
* @param et
* @return void
* @author Tophua
* @date 2019/8/3
*/
public void doLog(MappedStatement mappedStatement, Object et) {
// 反射获取实体类
Class<?> clazz = et.getClass();
// 不含有表名的实体就默认通过
if (!clazz.isAnnotationPresent(TableName.class)) {
return;
}
// 获取表名
TableName tableName = clazz.getAnnotation(TableName.class);
String tbName = tableName.value();
if (StringUtils.isBlank(tbName)) {
return;
}
String pkName = null;
String pkValue = null;
// 获取实体所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 设置些属性是可以访问的
field.setAccessible(true);
if (field.isAnnotationPresent(TableId.class)) {
// 获取主键
pkName = field.getName();
try {
// 获取主键值
pkValue = field.get(et).toString();
} catch (Exception e) {
pkValue = null;
} }
}
BasicInfo basicInfo = new BasicInfo(dataSource, (Model) et, tbName, pkName, pkValue); // 插入
if (SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {
InsertInfo insertInfo = new InsertInfo(basicInfo, et);
dataLogHandler.insertHandler(insertInfo);
}
// 更新
if (SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType()) && StringUtils.isNotBlank(pkName) && StringUtils.isNotBlank(pkValue)) {
Object oldObj = this.queryData(pkName, pkValue, (Model) et);
if (oldObj != null) {
UpdateInfo updateInfo = new UpdateInfo(basicInfo, oldObj, et);
// 调用自定义处理方法
dataLogHandler.updateHandler(updateInfo);
}
}
// 删除
if (SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType()) && StringUtils.isNotBlank(pkName) && StringUtils.isNotBlank(pkValue)) {
Object delObj = this.queryData(pkName, pkValue, (Model) et);
if (delObj != null) {
DeleteInfo deleteInfo = new DeleteInfo(basicInfo, delObj);
// 调用自定义处理方法
dataLogHandler.deleteHandler(deleteInfo);
}
}
} /**
* 根据主键和主键值查询数据
*
* @param pkName
* @param pkValue
* @param clazz
* @return java.lang.Object
* @author Tophua
* @date 2019/8/5
*/
private Object queryData(String pkName, String pkValue, Model clazz) {
// 查询更新前数据
return clazz.selectOne(Wrappers.query().eq(pkName, pkValue));
}
}

二、配置

 package com.erp4cloud.rerp.common.data.log;

 import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; /**
* 数据更新日志处理配置(实现按需加载)
*
* @author Tophua
* @date 2019/8/2
*/
@Configuration
@AllArgsConstructor
@ConditionalOnBean({DataSource.class, DataLogHandler.class})
public class DataLogConfig { private final DataLogHandler dataLogHandler;
private final DataSource dataSource; @Bean
@ConditionalOnMissingBean
public DataUpdateInterceptor dataUpdateInterceptor() {
return new DataUpdateInterceptor(dataSource, dataLogHandler);
}
}

提示:公共模块中需要在spring.factories(src/main/resources/META-INF/)中进行配置让Spring自动进行装配,小笔使用如下

 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.erp4cloud.rerp.common.data.log.DataLogConfig

三、在其它模块实现自定义处理接口

在接口中会根据不同操作传入不同参数,各位可以根据具体方法取出数据进行所需操作。

本例仅测试使用,未实现具体操作,在具体使用中请自行编写具体逻辑。

DataLogHandler 自定义处理接口,各模块实现,注意需要将实现类作为ServiceBean使用,否则该功能无法生效。

 package com.erp4cloud.rerp.common.data.log;

 /**
* 数据日志处理
*
* @author Tophua
* @date 2019/8/2
*/
public interface DataLogHandler { /**
* 插入处理
*
* @param insertInfo 插入数据信息
* @return void
* @author Tophua
* @date 2019/8/2
*/
void insertHandler(InsertInfo insertInfo); /**
* 更新处理
*
* @param updateInfo 更新数据信息
* @return void
* @author Tophua
* @date 2019/8/2
*/
void updateHandler(UpdateInfo updateInfo); /**
* 删除处理
*
* @param deleteInfo 删除数据信息
* @return void
* @author Tophua
* @date 2019/8/3
*/
void deleteHandler(DeleteInfo deleteInfo);
}

实现demo

 package com.erp4cloud.rerp.building.log;

 import com.erp4cloud.rerp.common.data.log.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service; import java.util.List; /**
* describe
*
* @author Tophua
* @date 2019/8/3
*/
@Service
@AllArgsConstructor
public class DataLogDeal implements DataLogHandler { ObjectMapper objectMapper = new ObjectMapper(); @SneakyThrows
@Override
public void insertHandler(InsertInfo insertInfo) {
System.out.println("插入:" + objectMapper.writeValueAsString(insertInfo.getInsertObj()));
} @SneakyThrows
@Override
public void updateHandler(UpdateInfo updateInfo) {
List<CompareResult> cr = updateInfo.getCompareResult();
StringBuilder sb = new StringBuilder();
sb.append("更新\"");
sb.append(updateInfo.getBasicInfo().getTbName());
sb.append("\" 表 ");
cr.forEach(r -> {
String s = "把《" + r.getFieldComment() + "》从<" + r.getOldValue() + ">改成<" + r.getNewValue() + ">";
sb.append(s);
});
System.out.println(sb.toString());
} @SneakyThrows
@Override
public void deleteHandler(DeleteInfo deleteInfo) {
System.out.println("删除:" + objectMapper.writeValueAsString(deleteInfo.getDeleteObj()));
}
}

四、其它代码

BaseDataLogHandler 基础处理抽象类,提供底层数据对比方法。

 package com.erp4cloud.rerp.common.data.log;

 import lombok.AllArgsConstructor;
import lombok.Getter; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional; /**
* 数据日志基础信息及处理
*
* @author Tophua
* @date 2019/8/5
*/
@Getter
@AllArgsConstructor
public abstract class BaseDataLogHandler { /**
* 数据基础信息
*/
private BasicInfo basicInfo; /**
* 对比两个对象
*
* @param oldObj 旧对象
* @param newObj 新对象
* @return java.util.List<com.erp4cloud.rerp.common.data.log.CompareResult>
* @author Tophua
* @date 2019/8/5
*/
protected List<CompareResult> compareTowObject(Object oldObj, Object newObj) throws IllegalAccessException {
List<CompareResult> list = new ArrayList<>();
//获取对象的class
Class<?> clazz1 = oldObj.getClass();
Class<?> clazz2 = newObj.getClass();
//获取对象的属性列表
Field[] field1 = clazz1.getDeclaredFields();
Field[] field2 = clazz2.getDeclaredFields();
//遍历属性列表field1
for (int i = 0; i < field1.length; i++) {
//遍历属性列表field2
for (int j = 0; j < field2.length; j++) {
//如果field1[i]属性名与field2[j]属性名内容相同
if (field1[i].getName().equals(field2[j].getName())) {
field1[i].setAccessible(true);
field2[j].setAccessible(true);
if (field2[j].get(newObj) == null) {
continue;
}
//如果field1[i]属性值与field2[j]属性值内容不相同
if (!compareTwo(field1[i].get(oldObj), field2[j].get(newObj))) {
CompareResult r = new CompareResult();
r.setFieldName(field1[i].getName());
r.setOldValue(field1[i].get(oldObj));
r.setNewValue(field2[j].get(newObj)); // 匹配字段注释
Optional o = this.basicInfo.getFieldInfos().stream()
.filter(f -> r.getFieldName().equals(f.getJFieldName())).findFirst();
if (o.isPresent()) {
r.setFieldComment(((FieldInfo) o.get()).getComment());
}
list.add(r);
}
break;
}
}
}
return list;
} /**
* 对比两个数据是否内容相同
*
* @param object1,object2
* @return boolean类型
*/
private boolean compareTwo(Object object1, Object object2) { if (object1 == null && object2 == null) {
return true;
}
if (object1 == null && object2 != null) {
return false;
}
if (object1.equals(object2)) {
return true;
}
return false;
} }

BasicInfo 基础信息,数据源,本表字段信息等。

 package com.erp4cloud.rerp.common.data.log;

 import cn.hutool.db.Db;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils; import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap; /**
* 基础信息
*
* @author Tophua
* @date 2019/8/5
*/
@Getter
public class BasicInfo {
private static ConcurrentHashMap<String, List<FieldInfo>> fields = new ConcurrentHashMap<>(); /**
* 数据源
*/
private DataSource dataSource;
/**
* mybatis数据底层
*/
private Model model;
/**
* 表名
*/
private String tbName;
/**
* 主键名称
*/
private String pkName;
/**
* 主键值
*/
private String pkValue; /**
* 表字段注释
*/
private List<FieldInfo> fieldInfos; public BasicInfo(DataSource dataSource, Model model, String tbName, String pkName, String pkValue) {
this.dataSource = dataSource;
this.model = model;
this.tbName = tbName;
this.pkName = pkName;
this.pkValue = pkValue;
} public List<FieldInfo> getFieldInfos() {
if (!fields.containsKey(this.tbName)) {
String query = "select column_name fieldName, column_comment comment from information_schema.columns" +
" where table_name = \"" + this.tbName + "\" and table_schema = (select database())";
try {
this.fieldInfos = Db.use(dataSource).query(query, FieldInfo.class);
} catch (SQLException e) {
this.fieldInfos = new ArrayList<>();
}
this.fieldInfos.forEach(f -> {
String caseName = this.columnToJava(f.getFieldName());
f.setJFieldName(StringUtils.uncapitalize(caseName));
});
fields.put(this.tbName, this.fieldInfos);
}
return fields.get(this.tbName);
} /**
* 列名转换成Java属性名
*/
private String columnToJava(String columnName) {
return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
}
}

FieldInfo 字段信息

 package com.erp4cloud.rerp.common.data.log;

 import lombok.Data;

 /**
* 字段信息
*
* @author Tophua
* @date 2019/8/5
*/
@Data
public class FieldInfo { /**
* 字段名
*/
private String fieldName;
/**
* java字段名
*/
private String jFieldName;
/**
* 注释
*/
private String comment;
}

CompareResult 字段对比结果

 package com.erp4cloud.rerp.common.data.log;

 import lombok.Data;

 /**
* 对比两个对象结果
*
* @author Tophua
* @date 2019/8/5
*/
@Data
public class CompareResult { /**
* 字段名
*/
private String fieldName;
/**
* 字段注释
*/
private String fieldComment;
/**
* 字段旧值
*/
private Object oldValue;
/**
* 字段新值
*/
private Object newValue;
}

InsertInfo 插入信息

 package com.erp4cloud.rerp.common.data.log;

 import lombok.Getter;

 /**
* 数据插入信息
*
* @author Tophua
* @date 2019/8/5
*/
@Getter
public class InsertInfo extends BaseDataLogHandler { /**
* 插入对象
*/
private Object insertObj; public InsertInfo(BasicInfo basicInfo, Object insertObj) {
super(basicInfo);
this.insertObj = insertObj;
} }

UpdateInfo 更新信息

 package com.erp4cloud.rerp.common.data.log;

 import lombok.Getter;

 import java.util.List;

 /**
* 数据更新信息
*
* @author Tophua
* @date 2019/8/5
*/
@Getter
public class UpdateInfo extends BaseDataLogHandler { /**
* 更新前对象
*/
private Object oldObj;
/**
* 更新对象
*/
private Object newObj; public UpdateInfo(BasicInfo basicInfo, Object oldObj, Object newObj) {
super(basicInfo);
this.oldObj = oldObj;
this.newObj = newObj;
} public List<CompareResult> getCompareResult() throws IllegalAccessException {
return compareTowObject(this.oldObj, this.newObj);
}
}

DeleteInfo 删除信息

package com.erp4cloud.rerp.common.data.log;

import lombok.Getter;

/**
* 数据删除信息
*
* @author Tophua
* @date 2019/8/5
*/
@Getter
public class DeleteInfo extends BaseDataLogHandler { /**
* 删除对象
*/
private Object deleteObj; public DeleteInfo(BasicInfo basicInfo, Object deleteObj) {
super(basicInfo);
this.deleteObj = deleteObj;
}
}

四、结果

更新时控制台打印:

 更新"customer_resource_base_info" 表 把《姓名》从<测试>改成<测试dsffgggg>把《身份证》从<2222222>改成<3333333333333> 

由于是测试所以未进行数据库保存,大家自行保存。

3、总结

本例主要解决多实体数据更新前后对比记录,当然也可使用AOP实现数据对比,但经笔者实现感觉还是此方法实现起来相对简单。笔者更推荐使用底层技术直接进行拦截处理,这样能保证任何数据操作都毫无遗漏,不放过任何操作。

目前本例暂未实现数据无主键更新记录,但业务中经常会出现无主键根据其它条件更新,所以本例还可进行优化提升,在此笔者就先放一段了,等后续再进行升级更新。

欢迎各位大神交流意见。。。。。。

SpringCloud或SpringBoot+Mybatis-Plus利用mybatis插件实现数据操作记录及更新对比的更多相关文章

  1. SpringCloud或SpringBoot+Mybatis-Plus利用AOP+mybatis插件实现数据操作记录及更新对比

    引文 本文主要介绍如何使用Spring AOP + mybatis插件实现拦截数据库操作并根据不同需求进行数据对比分析,主要适用于系统中需要对数据操作进行记录.在更新数据时准确记录更新字段 核心:AO ...

  2. Mysql之binlog日志说明及利用binlog日志恢复数据操作记录

    众所周知,binlog日志对于mysql数据库来说是十分重要的.在数据丢失的紧急情况下,我们往往会想到用binlog日志功能进行数据恢复(定时全备份+binlog日志恢复增量数据部分),化险为夷! 一 ...

  3. 【转】Mysql之binlog日志说明及利用binlog日志恢复数据操作记录

    众所周知,binlog日志对于mysql数据库来说是十分重要的.在数据丢失的紧急情况下,我们往往会想到用binlog日志功能进行数据恢复(定时全备份+binlog日志恢复增量数据部分),化险为夷! 废 ...

  4. Mysql利用binlog日志恢复数据操作(转)

    a.开启binlog日志:1)编辑打开mysql配置文件/etc/mys.cnf[root@vm-002 ~]# vim /etc/my.cnf在[mysqld] 区块添加 log-bin=mysql ...

  5. Mybatis分页-利用Mybatis Generator插件生成基于数据库方言的分页语句,统计记录总数 (转)

    众所周知,Mybatis本身没有提供基于数据库方言的分页功能,而是基于JDBC的游标分页,很容易出现性能问题.网上有很多分页的解决方案,不外乎是基于Mybatis本机的插件机制,通过拦截Sql做分页. ...

  6. springboot+mybatis+mysql 利用mybatis自动生成sql语句

    工具和环境 idea,mysql,JDK1.8 效果图如下 结构图如下 java resources sql文件 /* Navicat MySQL Data Transfer Source Serve ...

  7. SpringBoot入门项目CRM学习过程中的报错记录(更新ing)

    在用mybatis自动生成实体类和mapper时报错..... is unrecognized or represents more than one time zone. You must conf ...

  8. Springboot 系列(十一)使用 Mybatis(自动生成插件) 访问数据库

    1. Springboot mybatis 介绍 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数获取 ...

  9. Springboot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件

    前言 在 Springboot 系列文章第十一篇里(使用 Mybatis(自动生成插件) 访问数据库),实验了 Springboot 结合 Mybatis 以及 Mybatis-generator 生 ...

随机推荐

  1. Android SDK安装环境变量配置

    安卓tool: http://tools.android-studio.org/ SDK下载地址:http://dl.google.com/android/android-sdk_r24.4.1-wi ...

  2. jquery如何将信息遍历到界面上

    1.使用的时候一定要导入juqery库 1.1 这里放置一个cdn的库 <script src="https://cdn.staticfile.org/jquery/1.10.2/jq ...

  3. crowdfunding项目01——感人的错误

    四十搭环境,半个小时下载jar包,网速感人,一个半小时找bug真是感动 首先SSM项目,主要功能进行增删改查 建立父工程和子工程,产生依赖关系 父工程:pom 子工程:jar.war(web工程) 错 ...

  4. Mysql数据多表查询及pymysql的使用

    Exists关键字表示存在,在使用exists关键字时,内增查询语句不返回查询记录,而是返回一个真假值,True或者False,返回True外层语句才会进行查询:返回False时,外层查询语句不会进行 ...

  5. Mysql主键外键操作

    外键: ·一对多 ·多对多 ·一对一 ·修改表 ·复制表   主键:   rimary key auto_increment(primary key是主键通常和auto_increment自动增加混合 ...

  6. 关于testbench

    区别与verilog HDL代码,主要留意以下内容: 1,语言本身支持的特征和可综合的代码是两回事,不是所有verilog语言都可以转化为硬件的. 2,testbench作为top module,不需 ...

  7. 第三十五篇 入门机器学习——Juptyer Notebook中的常用快捷键

        1.运行当前Cell:Ctrl + Enter   2.运行当前Cell并在其下方插入一个新的Cell:Alt + Enter   3.运行当前Cell并选中其下方的Cell:Shift + ...

  8. Linux find命令:在目录中查找文件(超详解)

    find 是 Linux 中强大的搜索命令,不仅可以按照文件名搜索文件,还可以按照权限.大小.时间.inode 号等来搜索文件.但是 find 命令是直接在硬盘中进行搜索的,如果指定的搜索范围过大,f ...

  9. Python | 字符串拆分和拼接及常用操作

    一.字符串拆分 str = "hola ha1 ha2 china ha3 " # partition 从左侧找到第一个目标,切割成三组数据的[元组] str1 = str.par ...

  10. 使用maven构建项目时,SSM和springboot项目的打包与云服务器部署

    下面讲讲如何打包SSM和springboot项目,并部署到云服务器上. 由于使用的IDE不同,有的使用eclipse,有的使用idea,所以如果在IDE中按照 maven clean 再 maven ...