MyBatis(四)------MyBatis的生命周期及配置实例
目录
1、MyBatis的生命周期
1、SqlSessionFactoryBuilder
2、SqlSessionFactory
3、SqlSession
4、Mapper
2、MyBatis实例
1、配置log4j.properties日志
2、构建POJO对象
3、构建Mapper映射器
4、配置Mapper映射文件
5、配置mybatisConfig.xml全局基础文件
6、构建SqlSessionFactoryUtils工具类
7、测试
生命周期是组件的重要问题,尤其是在多线程的情况中,比如互联网应用、Socket请求等,而MyBatis也常用于多线程的环境中,错误使用会造成严重的多线程并发问题,因此,我们需要掌握MyBatis组件的生命周期。而MyBatis的生命周期就是每一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被JVM销毁,以免继续占用资源。
1、MyBatis的生命周期
1、SqlSessionFactoryBuilder
SqlSessionFactoryBuilder的作用在于创建SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder就失去作用,所以它只能存在于创建SqlSessionFactory的方法中,而不要让其长期存在。
2、SqlSessionFactory
SqlSessionFactory可以被认为是一个数据库连接池,它的作用是创建SqlSession接口对象。因为MyBatis的本质就是Java对数据库的操作,所以SqlSessionFactory的生命周期存在于整个MyBatis的应用中,所以一旦创建了SqlSessionFactory,就要长期保存它,直至不在使用MyBatis应用,所以可以认为SqlSessionFactory的生命周期就等同与MyBatis的应用周期。
由于SqlSessionFactory是对一个数据库的连接池,所以它占据着数据库的连接资源。如果创建多个SqlSessionFactory,那么就存在多个数据库连接池,不利于对数据库资源的控制,也将导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免出现这种情况。因此在一般的应用中,我们往往希望SqlSessionFactory作为一个单例,让它在应用中被共享。
3、SqlSession
如果说SqlSessionFactory相当于数据库连接池,那么SqlSession就相当于一个数据库连接(Connection对象),我们可以在一个事务中执行多条SQL,然后通过它的commit、rollback等方法,提交或回滚事务。所以SqlSession应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给SqlSessionFactory,否则数据库资源将被耗光,系统瘫痪,所以使用try...catch...finally语句来保证SqlSession正确关闭。
4、Mapper
Mapper是一个接口,它由SqlSession所创建,所以它的最大生命周期至多和SqlSession保持一致,当SqlSession关闭时,Mapper的数据库连接资源也会消失,所以Mapper的生命周期应该小于等于SqlSession的生命周期。Mapper代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关业务,就应该废弃它。
Mybatis组件生命周期如图所示:
2、MyBatis实例
这里我们做一个简单的实例,主要处理角色表的增、删、改、查,涉及的文件及其作用如下表 :
文件 | 作用 |
SysRoleMapper.java | Mapper映射器接口 |
SysRoleMapper.xml | Mapper映射器XML配置文件,描述映射关系、SQL等内容。【MyBatis核心文件】 |
SysRole.java | POJO对象 |
SqlSessionFactoryUtils.java | 会话工厂工具类,用于创建SqlSessionFactory和获取SqlSession对象 |
log4j.properties | 日志配置文件 |
mybatisConfig.xml | MyBatis的基础配置文件 |
SysRoleMapperConfigTest.java | 测试程序 |
1、配置log4j.properties日志
#全局配置
#优先级:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF
log4j.rootLogger=DEBUG, stdout
#设置为DEBUG级别,便于把详细的日志记录打印出来,便于调试。
#在生产中,可以设置为INFO级别
log4j.logger.org.mybatis.=DEBUG
#控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
我们设置为DEBUG级别,以能够把最详细的日志打印出来,便于调试,在生产中可设置为INFO级别。
2、构建POJO对象
构建一个POJO对象,最终查询会映射到它上面,或将其保存到数据库中
public class SysRole {private Integer id;private String name;private String description;private Date createTime;private Date updateTime;/*省略setter和getter方法*/
}
3、构建Mapper映射器
采用XML方式构建映射器,它包含一个Mapper接口和一个XML映射文件。这里要实现增、删、改、查。所以定义一个接口
SysRoleMapper.Java
/*** 映射器接口*/
public interface SysRoleMapper {/*[1.1]新增一条角色------->自定义主键*/public int insertRole(SysRole sysRole);/*[1.2]新增一条角色------->自增主键*/public int insertRole2(SysRole sysRole);/*[2]删除一条角色*/public int deleteRole(Integer id);/*[3]修改一条角色*/public int updateRole(SysRole sysRole);/*[4]查询一条角色*/public SysRole getRole(Integer id);/*[5]通过角色名获取整个角色列表*/public List<SysRole> findRoles(String roleName);
}
其中,insertRole表示插入一个SysRole对象;deleteRole表示删除;updateRole是修改一个SysRole对象;getRole获取一个SysRole对象;findRoles则是通过角色名称获取一个角色对象列表。我们要用一个XML文件来描述这些功能。
4、配置Mapper映射文件
<?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.mybatis.mapper.SysRoleMapper"><!--insert:新增/插入数据,=============================自定义主键=============================id:一般对应接口中定义的SQL方法名称;parameterType:指定传入的参数类型;子元素selectKey:支持自定义键值的生成规则。keyProperty:将返回的主键放入传入参数的Id中保存【指定采用哪个属性作为POJO的主键】;order:当前函数相对于SQL语句的执行顺序,在insert前执行的是BEFORE,在insert后执行的是AFTER由于mybatis的自增原理执行完SQL语句后才将主键生成,所以使用selectKey的执行顺序为AFTERresultType:返回的主键id的类型,也就是keyProperties中id属性的类型resultMap:用来描述从数据库结果集中来加载对象,它是最复杂、强大的元素【提供映射规则】如果要返回数据库自增主键:可以使用select LAST_INSERT_ID()执行 select LAST_INSERT_ID()数据库函数,返回自增的主键LAST_INSERT_ID():MySQL的函数,返回auto_increment自增列新记录id的值#{}占位符:如果传入的是pojo实体类型,占位符中的变量名称必须是pojo实体类中对应的属性.属性.属性...--><insert id="insertRole" parameterType="sysRole"><selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">select LAST_INSERT_ID()</selectKey>insert into sys_role(name,description,createTime,updateTime)values (#{name}, #{description}, #{createTime},#{updateTime})</insert><!-- insert:新增/插入数据,=============================自增主键============================= useGeneratedKeys:获取数据库生成的主键,如果存在多个主键,就用逗号隔开--><insert id="insertRole2" parameterType="sysRole" useGeneratedKeys="true" keyProperty="id">insert into sys_role(name,description)values (#{name}, #{description})</insert><!-- ===============================================删除=============================================== --><!-- 按ID删除,parameterType传入参数类型=========>POJO类中角色关联表对应的id字段参数的类型 --><delete id="deleteRole" parameterType="java.lang.Integer">delete from sys_role where id = #{id}</delete><!-- ===============================================修改=============================================== --><update id="updateRole" parameterType="sysRole">update sys_role set name = #{name},description = #{description}where id = #{id}</update><!-- ===============================================查询=============================================== --><!--<select>标签id:sql语句唯一标识,一般为Mapper接口类中的查询方法名parameterType:指定SQL方法传入的参数类型(或者指定传入的参数类型)resultType:指定返回结果集的类型,【常设置为实体类的全路径,返回指向的model类型】#{}占位符:起到占位作用,如果传入的是基本类型(string,long,double,int,boolean,float等),那么#{}中的变量名称可以随意写。resultType="sysRole",全局配置文件中,使用单个定义别名,也可以使用model实体类的全路径,与核心配置文件中<typeAlias>标签中的alias命名一致flushCache:调用SQL后,是否要求mybatis清空之前查询本地缓存和二级缓存,默认falseuseCache:启动二级缓存开关,是否将本次结果缓存,默认false--><!-- ============================================sql元素============================================== --><!-- 只需编写一次,就可以在其他元素中引用它了 --><sql id="roleCols">id,name,description</sql><select id="getRole" parameterType="java.lang.Integer" resultType="sysRole" flushCache="true">select id,role_name as roleName from sys_role where id = #{id}<!-- ==========>>>>>使用sql元素<<<<<========== --><!-- select <include refid="roleCols" /> from sys_role where id = #{id} --></select><!--按名称模糊查询 ,返回结果是集合返回结果为集合时,可以调用selectList方法,这个方法返回的结果就是一个集合,所以映射文件中应该配置成集合泛型的类型${}拼接符:字符串原样拼接,如果传入的参数是基本类型(string,long,double,int,boolean,float等),那么${}中的变量名称必须是value注意:拼接符有sql注入的风险,所以慎重使用resultType="sysRole",全局配置文件中,使用包扫描的方式批量定义别名,不区分大小写,也可以使用model实体类的全路径--><select id="findRoles" parameterType="String" resultType="sysRole">select id,name as name,description createTime as createTime,updateTime as updateTimewhere name like concat('%',#{name},'%')and description like concat('%',#{description},'%')</select>
</mapper>
这是一些比较简单的SQL语句,inser\delete、select、update、元素代表了增、删、改、查,而它们里面的元素id则标识了对应的SQL。paramterType标出了SQL返回值类型的参数,resultType则代表结果映射成为什么类型。
有了它们,我们就可以开始创建SqlSessionFactory了。首先来完成mybatisConfig.xml全局基础配置文件。
5、配置mybatisConfig.xml全局基础文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"><!-- ==========================================mybatis的基础配置============================================== -->
<configuration><!-- ===========================================properties配置============================================== --><!--♠♠方式1: 引用java属性的文件db.properties --><properties resource="db.properties" /><!--♠方式2: 在<propties>标签下的子元素<propertie>定义数据库配置的name和value如果属性参数有成千上百个,该方式不是最优的,建议使用方式1,通过java属性文件导入方式。<properties><proper name="database.driver" value="com.mysql.jdbc.Driver"></proper><proper name="database.url" value="jdbc:mysql://localhost:3306/boot_security?characterEncoding=utf-8"></proper><proper name="database.username" value="root"></proper><proper name="database.password" value="root"></proper></properties> --><!--♠♠♠方式3:代码传递方式传递参数创建SqlSessionFactory时,将配置信息重置到properties属性中。 --><!-- ===========================================Setting设置================================================= --><!-- <settings>====>开启驼峰规则与下划线间的映射关系<setting name="mapUnderscoreToCamelCase" value="true"/>设置jdbc与数据库的关系类型 mysql不需要配置 Oracle需要<setting name="jdbcTypeForNull" value="NULL"/>设置懒加载 优化速度 当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。默认值false<setting name="lazyLoadingEnabled" value="true"/>设置懒积极加载为消极加载 优化速度 <setting name="aggressiveLazyLoading" value="false"/>====>该配置影响的所有映射器中配置的缓存的全局开关。默认值true<setting name="cacheEnabled" value="true"/>是否允许单一语句返回多结果集(需要兼容驱动)。 默认值true<setting name="multipleResultSetsEnabled" value="true"/>使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。默认值true<setting name="useColumnLabel" value="true"/>====>允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。 默认值false <setting name="useGeneratedKeys" value="false"/>指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。 默认值PARTIAL<setting name="autoMappingBehavior" value="FULL"/><setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。默认SIMPLE <setting name="defaultExecutorType" value="SIMPLE"/>设置超时时间,它决定驱动等待数据库响应的秒数。<setting name="defaultStatementTimeout" value="25"/>====>允许在嵌套语句中使用分页(RowBounds)默认值False<setting name="safeRowBoundsEnabled" value="false"/>====>允许在嵌套语句中使用分页(ResultHandler)默认值False<setting name="safeResultHandlerEnabled" value="false"/>====>MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 <setting name="localCacheScope" value="SESSION"/>指定哪个对象的方法触发一次延迟加载。 <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/></settings> --><!-- ========================================typeAliases别名设置============================================= --><!-- 定义实体类的别名:系统定义别名,或者自定义别名 --><!--方式1:定义单个POJO实体类的别名引入POJO实体类的类全路径和别名后,其对应的映射文件中resutlType返回结果的类型应当为alise定义的别名type:实体类的全路径名称alias:别名<typeAlias type="com.mybatis.model.SysUser" alias="user"/> --><typeAliases><!-- 按ID查询用户,使用单个POJO实体类别名方式定义,对需要起别名的类单独配置 --><typeAlias alias="sysUser" type="com.mybatis.pojo.SysUser"/><typeAlias alias="sysRole" type="com.mybatis.pojo.SysRole" /></typeAliases><!--方式2:使用“包扫描”的方式批量定义别名,定义后别名等于类名,不区分大小写,建议按照驼峰命名约束进行命名可以配合mybatis的注解@Alias("xxx")进行区分,进而避免别名重名导致的扫描失败的问题<typeAliases><package name="com.mybatis.pojo" /></typeAliases>--><!-- ===============================================数据库环境配置============================================= --><!-- 数据库环境--><environments default="development"><environment id="development"><!-- 使用JDBC事务管理器--><transactionManager type="JDBC" /><!-- <transactionManager type="MANAGED" /> --><!-- 数据库连接池--><!-- <dataSource type="UNPOOLED">采用非数据库池的管理方式 --><!-- <dataSource type="JNDI"> --><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}" /><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /></dataSource></environment></environments><!-- ==========================================mapper映射器配置=============================================== --><!-- Mapper映射器,加载核心映射配置文件 --><mappers><!--方式1:通过文件路径引入映射器原始映射文件配置方式,映射文件的全路径,需要一一的书写,较繁琐映射文件和Mapper接口可以不用放在一个目录下--><mapper resource="mappers/SysRoleMapper.xml"/><mapper resource="mappers/SysUserMapper.xml"/><!--方式2:通过类注册方式引入映射器mapper接口代理方式,使用class属性引入接口的全路径名称:使用规则:1. 接口的名称和映射文件名称除扩展名外要完全相同2. 接口和映射文件要放在同一个目录下<mapper class="com.mybatis.mapper.SysRoleMapper"/><mapper class="com.mybatis.mapper.SysUserMapper"/>--><!--方式3:通过包扫描的方式批量引入映射器使用规则:1. 接口的名称和映射文件名称除扩展名外要完全相同2. 接口和映射文件要放在同一个目录下<package name="com.mybatis.mapper"/>--><!--方式4:使用映射文件引入映射器<mapper url="file:///var/mappers/mappers/SysRoleMapper.xml"/><mapper url="file:///var/mappers/mappers/SysUserMapper.xml"/>--></mappers>
</configuration>
使用mybatisConfig.xml文件,通过SqlSessionFactoryBuilder来构建SqlSessionFactory。由于SqlSessionFactory应该采用单例,代码如下:
6、构建SqlSessionFactoryUtils工具类
/*** SqlSessionFactoryUtils工具类,用于创建SqlSessionFactory和SqlSession对象*/
public class SqlSessionFactoryUtils {private final static Class<SqlSessionFactoryUtils> LOCK = SqlSessionFactoryUtils.class;// [3]定义会话工厂变量private static SqlSessionFactory sqlSessionFactory =null;//其他代码不能通过new的方式来创建它private SqlSessionFactoryUtils() {}private static SqlSessionFactory getSqlSessionFactory(){//锁,防止在多线程中多次实例化sqlSessionFactory对象,从而保证sqlSessionFactory的唯一性synchronized (LOCK) {if (sqlSessionFactory != null) {return sqlSessionFactory;}//[1]mybatis全局配置文件路径String str = "mybatisConfig.xml";InputStream inputStream;try {//[2]把全局配置文件放入流中inputStream = Resources.getResourceAsStream(str);//[4]通过SqlSessionFactoryBuilder构造器的build方法创建sqlSessionFactory会话工厂sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//关闭流inputStream.close();} catch (IOException e) {e.printStackTrace();}return sqlSessionFactory;}}//[5]通过会话工厂调用openSession方法,来创建sqlSession会话对象public static SqlSession openSqlSession(){if (sqlSessionFactory == null) {getSqlSessionFactory();}return sqlSessionFactory.openSession();}
}
构造方法中,加入private关键字,使得其他代码不能通过new的方式来创建它。而加入synchronized关键字加锁,主要是为了防止在多线程中多次实例化SqlSessionFactory对象,从而保证SqlSessionFactory的唯一性。而openSqlSession方法的作用则是创建SqlSession对象。
7、测试
public class SysRoleMapperConfigTest{private static Logger log = Logger.getLogger(SysRoleMapperConfigTest.class);/*=====================================插入一条角色记录=====================================*/@Testpublic void insertRole1() throws Exception{SqlSession sqlSession = null;try {sqlSession = SqlSessionFactoryUtils.openSqlSession();SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);SysRole sysRole = new SysRole();// sysRole.setId(23);sysRole.setName("root2");sysRole.setDescription("root2");sysRole.setCreateTime(new Date());sysRole.setUpdateTime(new Date());int i = roleMapper.insertRole(sysRole);log.info("==>插入角色记录的结果:" + i);sqlSession.commit();} catch (Exception e) {sqlSession.rollback();} finally {if (sqlSession != null) {sqlSession.close();}}}@Testpublic void insertRole2()throws Exception{SqlSession sqlSession = null;try {sqlSession = SqlSessionFactoryUtils.openSqlSession();SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);SysRole sysRole = new SysRole();sysRole.setName("root1000");sysRole.setDescription("root1000");int i = roleMapper.insertRole2(sysRole);log.info("========>>>>" + i);sqlSession.commit();} catch (Exception e) {sqlSession.rollback();} finally {if (sqlSession != null) {sqlSession.close();}}}/*=====================================删除一条角色记录=====================================*/@Testpublic void deleteRole(){SqlSession sqlSession = null;try {sqlSession = SqlSessionFactoryUtils.openSqlSession();SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);int i = roleMapper.deleteRole(20);log.info("==>删除角色记录的结果:" + i);sqlSession.commit();} catch (Exception e) {sqlSession.rollback();} finally {if (sqlSession != null) {sqlSession.close();}}}/*=====================================修改一条角色记录=====================================*/@Testpublic void updateRole() throws Exception{SqlSession sqlSession = null;try {sqlSession = SqlSessionFactoryUtils.openSqlSession();SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);SysRole sysRole = new SysRole();sysRole.setId(22);sysRole.setName("westone");sysRole.setDescription("westone");int i = roleMapper.updateRole(sysRole);log.info("==>修改角色记录的结果:" + i);sqlSession.commit();} catch (Exception e) {sqlSession.rollback();} finally {if (sqlSession != null) {sqlSession.close();}}}/*=====================================查询一条角色记录=====================================*/@Testpublic void getRoleById() throws Exception{SqlSession sqlSession = null;try {//获取会话对象sqlSession = SqlSessionFactoryUtils.openSqlSession();//加载Mapper接口类SysRoleMapper roleMapper = sqlSession.getMapper(SysRoleMapper.class);SysRole sysRole = roleMapper.getRole(1);log.info("==>根据ID查询的结果:" + sysRole.getName());//提交事务sqlSession.commit();} catch (Exception e) {//操作出现错误时,就回滚sqlSession.rollback();}finally{if (sqlSession != null) {sqlSession.close();}}}
}
愿你就像早晨八九点钟的太阳,活力十足,永远年轻。
MyBatis(四)------MyBatis的生命周期及配置实例相关推荐
- Angular 个人深究(四)【生命周期钩子】
Angular 个人深究(四)[生命周期钩子] 定义: 每个组件都有一个被 Angular 管理的生命周期. Angular 创建它,渲染它,创建并渲染它的子组件,在它被绑定的属性发生变化时检查它,并 ...
- Vue 生命周期探索:第四篇:生命周期-销毁流程
文章目录 探索学习 Vue 生命周期篇 第四篇:生命周期-销毁流程 生命周期_销毁流程 1. beforeDestory (销毁前) 2. Teardown watchers,child compon ...
- ES生命周期管理 配置ilm策略
ES声明周期通常分为四个阶段,hot阶段:索引有大量的写入和读取:warm阶段:索引没有写入,有大量的读取:code阶段:索引没有写入,有少量读取:delete阶段:删除索引或者关闭索引. 可以使用i ...
- Spring全家桶(四)Bean的生命周期
十.Bean生命周期 10.1 Bean的生命周期 Spring IOC容器可以管理Bean的生命周期,也允许在Bean生命周期的特定点执行定制的任务. Spring IOC容器对Bean的生命周期进 ...
- 微信小程序把玩(四)应用生命周期
为什么80%的码农都做不了架构师?>>> App() 函数用来注册一个小程序,注意必须在 app.js 中注册,且不能注册多个. 使用方式也跟Android中的Applicat ...
- (第四篇)Vue生命周期
生命周期 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8 ...
- Kubernetes(k8s)四、Pod生命周期(初始化容器的应用,探针liveness、readliness应用,)
Pod生命周期 学习目标:初始化容器的应用及两个探针的应用 探针 是由 kubelet 对容器执行的定期诊断: Kubelet 可以选择是否执行在容器上运行的三种探针执行和做出反应: liveness ...
- 四、项目生命周期和开发生命周期
一.简介 我们知道项目是暂时性.临时性的工作,具有开始时间和结束时间.正如达尔文进化论与马克思主义哲学认为:世界上任何事物都有其产生.发展和灭亡的过程(自然生命周期).项目同样有其生命周期,即开始.计 ...
- Vue的生命周期(Vue实例产生过程)
1. 挂载(初始化相关属性) beforeCreate 在实例初始化之后,数据观测和事件配置之前被调用 created(重要)(获取服务器数据的最佳时期) 在实例创建完成后被立即调用,实例已完成以下的 ...
- MyBatis(四)MyBatis插件原理
MyBatis插件原理 MyBatis对开发者非常友好,它通过提供插件机制,让我们可以根据自己的需要去增强MyBatis的功能.其底层是使用了代理模式+责任链模式 MyBatis官方https://m ...
最新文章
- OpenCV | OpenCV哈里斯 (Harris)角点检测
- 管理数据,应用程序和主机安全-B
- Android全局修改字体大小,Android 仿微信全局字体大小调整
- VS中添加命令行参数的方法
- 边缘设备上的实时AI人员检测:以实时模式检测视频中的人员
- 什么是软件测试资产,观点:当前是国内推广软件资产管理的最佳时期
- JAVA实现Token学习笔记001--Token入门案例
- C#dataGridView字体显示设置
- mapreduce 的partitioner,GroupComparator,KeyComparator,分布式缓存使用示例
- 数学建模(一)—— 人口增长模型的确定
- 微信公众号推文新手排版快速入门
- Windows 2000 检测系统安全进阶详解
- 全国计算机二级考试公共知识
- 救救小王吧:如何快速解决图像相似性检测问题?
- MDI窗体的优化---下
- PHP快速入门(一)
- React使用过程知识点随手记
- Ubuntu 16.04 kinetic安装教程
- win10开机右下角网络图标突然变成小地球,显示无internet连接,但是可以正常上网
- VMwere虚拟机 修改ip地址,设置指定ip