JAVA常用知识总结(六)——Mybatis
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
物理分页和逻辑分页的区别?
mybatis自带的分页RowBounds;//逻辑分页
Java:
RowBounds rb=new RowBounds(offset, limit); //offset(从多少条开始);limit(获取多少条) SqlSession sqlSession=sqlSessionFactory.openSession();//通过读取mybatis配置文件的输入流然后通过new SqlSeesionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));最终得到SqlSessionFactory List<Student> studentlist=sqlSession.selectList("xx.xx.Mapper.findStudent",null,rb);//第一个参数为具体Mapper文件的下的findStudent的ID,第二个参数为提供的条件参数,第三个参数为我们要进行对获取学生进行分页 sqlSession.close(); return studentlist;
Mapper: <select id="findStudent" resultType="Student"> select * from Student </select>
备注:通过以上例子,很明显的看出,在分页的时候,我们是把所有的数据都查询出来,然后通过RowBounds进行在内存分页.通过源码查看,也是通过ResuleSet结果集进行分页;
mybatis自写sql或者通过分页插件PageHelper: //物理分页
Java: List<Student> findStudent(); Service层: PageHelper.startPage(pageNum,pageSize);//pageNum 页数 pageSize 数量 List<Student> stu=studentDao.findStudent(); //studentDao @Autowried注解获取; 在执行查询数据时,就会自动执行2个sql;执行上述Mapper下的ID为findStudent的sql 自动执行分页,通过PageHelper进行识别是何数据库拼接分页语句,若是mysql,自动通过limit分页,若是oracle自动通过rownum进行分页,另一个会自动拼接Mapper下不存在的ID为findStudent_COUNT,查询的总数;可以通过打印的日志进行跟踪; PageInfo<Student> page = new PageInfo<Student>(stu); //自动封装总数count以及分页,数据返回页面 return page;//返回分页之后的数据 Mapper: <select id="findStudent" resultType="Student"> select * from Student </select>
备注:查看如上例子代码,我们就发现了是直接通过SQL进行在数据库中直接分页,得到的数据就是我们想要分页之后的数据,就是物理分页;
总结:
1:逻辑分页 内存开销比较大,在数据量比较小的情况下效率比物理分页高;在数据量很大的情况下,内存开销过大,容易内存溢出,不建议使用
2:物理分页 内存开销比较小,在数据量比较小的情况下效率比逻辑分页还是低,在数据量很大的情况下,建议使用物理分页
Mybatis是如何进行分页的?分页插件的原理是什么?
在使用Java Spring开发的时候,Mybatis算是对数据库操作的利器了。不过在处理分页的时候,Mybatis并没有什么特别的方法,一般需要自己去写limit子句实现,成本较高。好在有个PageHelper插件。
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10); //如果放外面就会导致 PageHelper 生产了一个分页参数,但是没有被消费,只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 当前写的这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
eg:select * from student,拦截sql后重写为:select t.* from (select * from student)t limit 0,10
MyBatis自定义插件(Plugin)详解?
1:实现类SQLStatsInterceptor实现接口Interceptor
import java.sql.Connection;
import java.util.Properties; import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class}) })
public class SQLStatusInterceptor implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
logger.info("mybatis intercept sql:", sql);
return invocation.proceed();
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
String dialect = properties.getProperty("dialect");
logger.info("mybatis intercept dialect:", dialect);
}
}
代码分析:
(1)首先SQLStatsInterceptor类实现了接口Interceptor;
(2)需要重写3个方法,核心的拦截处理方法是intercept,在这个方法中可以获取到对应的绑定的sql,在这里作为演示只是打印了SQL,如果需要可以保存起来。
(3)在方法上有一个很重要的注解@Intercepts,在此注解上配置的注解说明了要拦截的类(type=StatementHandler.class),拦截的方法(method="prepare"),方法中的参数(args={Connection.class})①,也就是此拦截器会拦截StatementHandler类中的如下方法:
public interface StatementHandler { Statement prepare(Connection connection)
throws SQLException; void parameterize(Statement statement)
throws SQLException; void batch(Statement statement)
throws SQLException; int update(Statement statement)
throws SQLException; <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }
不光可以拦截StatementHandler,总共可以拦截的类有:
StatementHandler (prepare, parameterize, batch, update, query)
ResultSetHandler (handleResultSets, handleOutputParameters)
ParameterHandler (getParameterObject, setParameters)
Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
2:这样一个插件就开发完成了,接下来需要在 mybatis-config.xml ②文件中增加 plugins节点,完整配置如下:
<configuration>
<!-- 插件管理 -->
<plugins>
<plugin interceptor="com.xx.common.interceptor.SQLStatusInterceptor">
<property name="dialect" value="mysql" />
</plugin>
</plugins> </configuration>
到这里就可以测试使用了,Mybatis插件实现了拦截器的功能。一旦懂了自定义Plugin后,那么对于PageHelper的实现也就明白了七八分了。
TKMybatis的原理?
tkmybatis是在mybatis框架的基础上提供了很多工具,让开发更加高效
数据源的配置,只需要将org.mybatis.spring.mapper.MapperScannerConfigurer改成tk.mybatis.spring.mapper.MapperScannerConfigurer
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">③
<property name="basePackage" value="com.cnten.**.dao"/>
<property name="sqlSessionFactoryBeanName" value="masterDbSqlSessionFactory" /> //mapper接口可以直接调用
<property name="markerInterface" value="tk.mybatis.mapper.common.Mapper"/>
</bean>
此框架为我们实现这些功能所有的改动都在Mapper层面,所有的Mapper都继承了tk.mybatis.mapper.common.Mapper
public interface TemplateMapper extends Mapper<Template>{ }
Mapper接口的声明如下,可以看到Mapper接口实现了所有常用的方法
public interface Mapper<T> extends
BaseMapper<T>,
ExampleMapper<T>,
RowBoundsMapper<T>,
Marker { }
看一下完整的UML图,太大了,可以用新窗口打开,放大之后再看
这里选择一个接口UpdateByPrimaryKeyMapper 对源码进行分析:
public interface UpdateByPrimaryKeyMapper<T> { @UpdateProvider(type = BaseUpdateProvider.class, method = "dynamicSQL")
int updateByPrimaryKey(T record); }
注解中的参数:
type参数指定的Class类,必须要能够通过无参的构造函数来初始化;
method参数指定的方法,必须是public的,返回值必须为String,可以为static。
Mybatis3中增加了使用注解来配置Mapper的新特性,有@SelectProvider、@UpdateProvider、@InsertProvider和@DeleteProvider 用于灵活的设置sql来源,这里设置了服务提供类和方法,但这个库并没有直接用method指定的方法来返回sql,而是在运行时进行解析的,代码如下
public class BaseUpdateProvider extends MapperTemplate { public BaseUpdateProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
} public String updateByPrimaryKey(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
sql.append(SqlHelper.updateTable(entityClass, tableName(entityClass)));
sql.append(SqlHelper.updateSetColumns(entityClass, null, false, false));
sql.append(SqlHelper.wherePKColumns(entityClass));
return sql.toString();
}
}
到这里我们就大概知道了这个库为我们提供便利的原理了,总的来说就是这个库帮我们提供了对表的基本操作的sql,帮我们省了很多工作量,而且维护起来也很方便,否则我们的xml文件动不动就几百行甚至上千行
对源码的探索不能到这里停止,最起码要分析到与另一个框架的整合点
我们知道,mybatis的mapper接口是在启动的时候被框架以JdkProxy的形式封装了的,具体对应的类是MapperFactoryBean,这个类中有一个checkDaoConfig()方法,是从父类继承并重写了该方法,继承结构如下
MapperFactoryBean -> SqlSessionDaoSupport -> DaoSupport
这里的DaoSupport就是spring提供的Dao的抽象,代码如下
public abstract class DaoSupport implements InitializingBean { // spring 完成属性设置后会调用此方法
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 这里提供了接口供子类去实现
checkDaoConfig(); // Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
} protected abstract void checkDaoConfig() throws IllegalArgumentException; protected void initDao() throws Exception {
} }
框架自定义的MapperFactoryBean重写了checkDaoConfig()方法,完成对所有sql语句的设置,代码如下
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
//通用Mapper
if (mapperHelper.isExtendCommonMapper(getObjectType())) {
//这里去处理该类所对应的MappedStatement,封装在helper类中处理
mapperHelper.processConfiguration(getSqlSession().getConfiguration(), getObjectType());
}
}
MapperHelper的processConfiguration方法如下
public void processConfiguration(Configuration configuration, Class<?> mapperInterface) {
String prefix;
if (mapperInterface != null) {
prefix = mapperInterface.getCanonicalName();
} else {
prefix = "";
}
for (Object object : new ArrayList<Object>(configuration.getMappedStatements())) {
if (object instanceof MappedStatement) {
MappedStatement ms = (MappedStatement) object;
//检查这个MappedStatement是否属于此映射对象
if (ms.getId().startsWith(prefix) && isMapperMethod(ms.getId())) {
if (ms.getSqlSource() instanceof ProviderSqlSource) {
//去设置该statement的sql语句
setSqlSource(ms);
}
}
}
}
}
设置sql的逻辑,提供了几种不同类型的sqlsource
/**
* 重新设置SqlSource
*
* @param ms
* @throws java.lang.reflect.InvocationTargetException
* @throws IllegalAccessException
*/
public void setSqlSource(MappedStatement ms) throws Exception {
if (this.mapperClass == getMapperClass(ms.getId())) {
throw new RuntimeException("请不要配置或扫描通用Mapper接口类:" + this.mapperClass);
}
Method method = methodMap.get(getMethodName(ms));
try {
//第一种,直接操作ms,不需要返回值
if (method.getReturnType() == Void.TYPE) {
method.invoke(this, ms);
}
//第二种,返回SqlNode
else if (SqlNode.class.isAssignableFrom(method.getReturnType())) {
SqlNode sqlNode = (SqlNode) method.invoke(this, ms);
DynamicSqlSource dynamicSqlSource = new DynamicSqlSource(ms.getConfiguration(), sqlNode);
setSqlSource(ms, dynamicSqlSource);
}
//第三种,返回xml形式的sql字符串
else if (String.class.equals(method.getReturnType())) {
String xmlSql = (String) method.invoke(this, ms);
SqlSource sqlSource = createSqlSource(ms, xmlSql);
//替换原有的SqlSource
setSqlSource(ms, sqlSource);
} else {
throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void,SqlNode,String三种!");
}
//cache
checkCache(ms);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException() != null ? e.getTargetException() : e);
}
}
到这里整个sql的获取流程就分析完了,可以节省开发的工作量,而且DAO层的结构更加清晰简洁
#{}和${}的区别是什么?
#将传入的数据当成一个字符串,会对自动传入的数据加一个双引号。例如
order by #id#,如果传入的值是111,那么解析成sql时的值变为order by "111",如果传入的值是id,在解析成sql为order by "id"
其实原sql语句通常写成 order by #{id} 与order by #id#的效果一样
- $将传入的数据直接显示在sql语句中。例如 order by ${id},如果传入的值是9则解析成sql语句为order by 9
- #方式能够很大程度上防止sql注入,而$无法防止sql的注入,
$一般用于传入数据库对象,例如传入表名
一般能用#就别用$
mybatis排序时使用order by动态参数时需要注意,使用$而不是#
Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>、<if>、<foreach>等
<?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.cnten.platform.common.expExcel.tpl.dao.ExcelTplMapper" >
<!-- 库表和对象关系映射 -->
<resultMap id="BaseResultMap" type="com.cnten.platform.common.expExcel.tpl.model.ExcelTpl" >
<id column="TPL_ID" property="tplId" jdbcType="VARCHAR" />
<result column="TPL_CODE" property="tplCode" jdbcType="VARCHAR" />
<result column="TPL_NAME" property="tplName" jdbcType="VARCHAR" />
<result column="EXCEL_NAME" property="excelName" jdbcType="VARCHAR" />
<result column="STATE" property="state" jdbcType="INTEGER" />
</resultMap> <!-- 用户基本字段 -->
<sql id="Base_Column_List" >
TPL_ID, TPL_CODE, TPL_NAME, EXCEL_NAME, STATE
</sql> <!-- 根据模板编码 获取 相关信息 -->
<select id="getExcelTplByCode" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List" />
from ct_excel_tpl
where TPL_CODE = #{tplCode,jdbcType=VARCHAR }
</select> <select id="getExcelTpl" resultMap="BaseResultMap" parameterType="java.lang.String">
select
<include refid="Base_Column_List" />
from ct_excel_tpl
where TPL_ID = #{tplId,jdbcType=VARCHAR }
</select> <select id="getAllExcelTpls" resultMap="BaseResultMap" parameterType="com.cnten.platform.common.expExcel.tpl.model.ExcelTpl">
select
<include refid="Base_Column_List" />
from ct_excel_tpl
where 1 = 1
<if test="tplCode != null and tplCode != ''">
and TPL_CODE like "%"#{tplCode,jdbcType=VARCHAR}"%"
</if>
<if test="tplName != null and tplName != ''" >
and TPL_NAME like "%"#{tplName,jdbcType=VARCHAR}"%"
</if>
</select> <delete id="batchDeleteExcelTpls" parameterType="java.util.List">
delete from ct_excel_tpl where TPL_ID in
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete> <insert id="insert" parameterType="com.cnten.platform.common.expExcel.tpl.model.ExcelTpl" >
insert into ct_excel_tpl (
TPL_ID, TPL_CODE, TPL_NAME, EXCEL_NAME, STATE
)values (#{tplId,jdbcType=VARCHAR },
#{tplCode,jdbcType=VARCHAR },
#{tplName,jdbcType=VARCHAR },
#{excelName,jdbcType=VARCHAR },
#{state,jdbcType=INTEGER }
)
</insert> <update id="updateByPrimaryKey" parameterType="com.cnten.platform.common.expExcel.tpl.model.ExcelTpl" >
update ct_excel_tpl
set
TPL_CODE = #{tplCode,jdbcType=VARCHAR },
TPL_NAME = #{tplName,jdbcType=VARCHAR },
EXCEL_NAME = #{excelName,jdbcType=VARCHAR },
STATE = #{state,jdbcType=VARCHAR }
where TPL_ID = #{tplId,jdbcType=VARCHAR }
</update>
</mapper>
最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。
Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。
public interface ExcelTplMapper { List<ExcelTpl> getAllExcelTpls(ExcelTpl excelTpl); int insert(ExcelTpl excelTpl); int updateByPrimaryKey(ExcelTpl excelTpl); void batchDeleteExcelTpls(List<String> ids); ExcelTpl getExcelTpl(String tplId); ExcelTpl getExcelTplByCode(String tplCode);
}
mybatis几个新特性?
从3.4.0开始,mybatis提供对外部表的alias引用方法,多表联合查询就方便多了,我们先看原始的方式是怎样做的
select a.id,a.name,b.bid,b.bname .....
from user a
left join room b
新特性
select id="selectUsers" resultType="map">
select
<include refid="user_col_sql_id"><property name="alias" value="t1"/>,
<include refid="room_col_sql_id"><property name="alias" value="t2"/>
from user t1
left join room t2
</select>
注释一:这里需要看mybatis的版本,3.4.0之后的分页为 args = { Connection.class,Integer.class},具体看 StatementHandler接口的参数的写法;
注释二:本例是基于SpringMVC的配置型写法,在SpringBoot中,可以定义配置类进行注入
import java.util.Properties; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class MyBatisConfiguration {
@Bean
public SQLStatusInterceptor sqlRecordInterceptor() {
SQLStatusInterceptor sqlRecordInterceptor = new SQLStatusInterceptor();
Properties properties = new Properties();
properties.setProperty("dialect","mysql");
sqlRecordInterceptor.setProperties(properties);
return sqlRecordInterceptor;
}
}
注释三:
basePackage 属性是让你为映射器接口文件设置基本的包路径。 你可以使用分号或逗号 作为分隔符设置多于一个的包路径。每个映射器将会在指定的包路径中递归地被搜索到。
MapperScannerConfigurer 属性不支持使用了 PropertyPlaceholderConfigurer 的属 性替换,因为会在 Spring 其中之前来它加载。但是,你可以使用 PropertiesFactoryBean 和 SpEL 表达式来作为替代。
如果你使 用了一个 以上的 DataSource ,那 么SqlSessionFactory 自动 装配可 能会失效 。这种 情况下 ,你可 以使用 sqlSessionFactoryBeanName 或 sqlSessionTemplateBeanName 属性来设置正确的 bean 名 称来使用。这就是它如何来配置的,注意 bean 的名称是必须的,而不是 bean 的引用,因 此,value 属性在这里替代通常的 ref:
说到mappper接口可以直接调用的原因,接下来就是上文提到的:
MapperFactoryBean
为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现:MapperFactoryBean。
这个类 可以让你直接注入数据映射器接口到你的 service 层 bean 中。当使用映射器时,你仅仅如调 用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码,因为 MyBatis-Spring 将会为你创建代理。
上面的配置有一个很大的缺点,就是系统有很多的配置文件时 全部需要手动编写,所以上述的方式替换成MapperScannerConfigurer 形式的配置
JAVA常用知识总结(六)——Mybatis的更多相关文章
- 总结Java常用到的六个加密技术和代码
加密,是以某种特殊的算法改变原有的信息数据,使得未授权的用户即使获得了已加密的信息,但因不知解密的方法,仍然无法了解信息的内容.大体上分为双向加密和单向加密,而双向加密又分为对称加密和非对称加密(有些 ...
- java基础知识(六)日期处理
一.日期处理类 在 JDK 1.1 之前,类 Date 有两个其他的函数.它允许把日期解释为年.月.日.小时.分钟和秒值.它也允许格式化和解析日期字符串.不过,这些函数的 API 不易于实现国际化.从 ...
- java新知识系列 六
sleep和wait的区别有: Servlet方法的使用 方法重写的规则,以及两同两小一大原则: DispatcherServlet的解析 依赖注入DU和控制反转Ioc AOP和OOP的区别 Spri ...
- JAVA常用知识总结(十三)——数据库(三)
Mysql的主键选择(主键自增,UUID,snowflake)? 使用自增长做主键的优点:1.很小的数据存储空间2.性能最好3.容易记忆使用自增长做主键的缺点:1.如果存在大量的数据,可能会超出自增长 ...
- JAVA常用知识总结(十一)——数据库(一)
项目中用到的不常见sql语法 1:空值不在前的排序 select a.* from WZX_SCZY A order by SCZY_START_TIME desc nulls last (不加nul ...
- JAVA常用知识总结(七)——Spring
如果一个接口有2个不同的实现, 如何Autowire某一个指定的实现? 1.通过增加@Qualifier(实现类的名字): @Autowired @Qualifier("GirlStuden ...
- JAVA常用知识总结(三)——JAVA虚拟机
先附一张JAVA虚拟机内存结构图: 其中JAVA虚拟机的线程问题<为什么JAVA虚拟机分为线程共享和非线程共享?>一文中已经有详细介绍,本文从面试中常问的一些JAVA虚拟机问题出发,主要从 ...
- Java基础知识陷阱(六)
本文发表于本人博客. 上次说了下equals跟==的问题,今天再来认识一下这个equals()跟hasCode().上次的代码如下: class Person{ public String name; ...
- JAVA常用知识总结(十四)——Servlet
Servlet属于线程安全的吗? Servlet不是线程安全的! 谈谈转发和重定向的区别 请求转发: request.getRequestDispatcher("/king_l2lu.jsp ...
随机推荐
- spark通信原理
https://github.com/apache/spark/tree/master/core/src/main/scala/org/apache/spark/network https://git ...
- 端口扫描 开启 防火墙 iptables SELinux
Linux 如何打开端口 - lclc - 博客园 https://www.cnblogs.com/lcword/p/5869522.html linux如何查看端口相关信息_百度经验 https:/ ...
- Linux时间子系统之三:时间的维护者:timekeeper【转】
本文转载自:http://blog.csdn.net/droidphone/article/details/7989566 本系列文章的前两节讨论了用于计时的时钟源:clocksource,以及内核内 ...
- 各种DP总结
一.数位DP 1.含有或不含某个数“xx”: HDU3555 Bomb HDU2089 不要62 2.满足某些条件,如能整除某个数,或者数位上保持某种特性: HDU3652 B-number Code ...
- WinDbg调试高内存的.Net进程Dump
WinDbg的学习路径,艰难曲折,多次研究进展不多,今日有所进展,记录下来. 微软官方帮助文档非常全面:https://msdn.microsoft.com/zh-cn/library/windows ...
- React引入,运行
1.引入 <script src="https://cdn.bootcss.com/react/15.5.4/react.min.js"></script> ...
- oracle:数据库版本问题导致的bug
公司开发出来的系统,由于各现场oracle数据库版本有10.2.0.4.11.2.0.1.11.2.0.3.11.2.0.4: 进而会导致版本不一导致错误问题.下面列举2个: 1.wm_concat ...
- Ubuntu上命令行下卸载软件
sudo apt-get --purge remove 软件名 (加了--purge表示会删除配置) sudo apt-get autoremove (这个命令后面文章有解释) dpkg -l (查看 ...
- limit的用法
limit子句可以用于强制select语句返回指定的记录数.limit接受一个或两个数字参数,参数必须是整数常量.如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最 ...
- wsdl 生成代码 WCF
具体方法: 打开Microsoft Visual Studio 2008->Visual Studio Tools->Visual Studio 2008 命令提示窗口. 输入:wsdl ...