批处理方法的 myBatis 映射器映射文件
#mybatis常见批量处理
在开发当中,可能经常会遇到批量处理这种情况,一般都再在java层面进行,
其本质是节省数据库连接打开关闭的的次数,占用更少的运行内存。
mybatis批量插入
<insert id="saveFeeRuleList" useGeneratedKeys="true" parameterType="java.util.List"> <selectKey resultType="java.lang.String" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO t_product_fee_rule( <include refid="Base_Column_List"/> ) VALUES <foreach collection="list" item="item" index="index" separator=","> ( #{item.id},#{item.productId}, #{item.feeCode},#{item.feeValue}, #{item.remarks} ) </foreach> </insert>
mybatis批量删除
<delete id="removeProductAgent" parameterType="java.util.HashMap"> <foreach collection="maps.agentIds" item="item" index="index" open="" close="" separator=";"> DELETE FROM t_product_agent WHERE 1 = 1 AND product_id = #{maps.productId} AND agent_id = #{item} </foreach> </delete>
此处的maps接口中的@Param值对应,属于自定义变量。
void removeProductAgent(@Param("maps")Map<String, Object> map);
mybatis批量修改
<update id="saveUpdateFeeRuleList" parameterType="java.util.List"> <foreach collection="list" item="item" index="index" open="" close="" separator=";"> UPDATE t_product_fee_rule SET fee_value = #{item.feeValue}, remarks = #{item.remarks} WHERE id = #{item.id} </foreach> </update>
myBatis mapper文件详解
本文的写作目的主要是带大家了解mapper的写法
表结构:
CREATE TABLE customer ( id int(11) NOT NULL COMMENT ‘企业用户ID', name varchar(45) DEFAULT NULL COMMENT ‘名称', logo varchar(80) DEFAULT ‘' COMMENT ‘企业标识', describe varchar(500) DEFAULT ‘' COMMENT ‘企业班车说明', is_enable tinyint(1) DEFAULT ‘0' COMMENT ‘是否启用 1=启用 0=不启用', phone varchar(20) DEFAULT NULL COMMENT ‘客服电话', admin varchar(50) DEFAULT NULL COMMENT ‘管理员账号', password varchar(80) DEFAULT NULL COMMENT ‘管理员密码', uuid varchar(80) DEFAULT NULL COMMENT ‘企业唯一ID', PRIMARY KEY (id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ;
Mapper映射文件是在实际开发过程中使用最多的。
Mapper文件中包含的元素有
-
cache
– 配置给定命名空间的缓存。 -
cache-ref
– 从其他命名空间引用缓存配置。 -
resultMap
– 映射复杂的结果对象。 -
sql
– 可以重用的 SQL 块,也可以被其他语句引用。 -
insert
– 映射插入语句 -
update
– 映射更新语句 -
delete
– 映射删除语句 -
select
– 映射查询语句
本文的代码都是用mybatis-generator生成的注释部分是博主自己加的:
<?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"> <!-- namespace绑定了与之对应的接口,值是该接口的全限定名;这个参数有且只有一个 --> <mapper namespace="cn.rainbowbus.dao.CustomerMapper"> <!-- 用来描述select语句返回字段与java属性的映射关系。 可以有多个resultMap标签,用不同id区分不同标签。 可以实现一对多,多对多关系 --> <resultMap id="BaseResultMap" type="cn.rainbowbus.entity.Customer"> <!-- column是表中的字段名。 property是对应的java属性。 jdbcTyep: 数据库中字段类型,它与Java中属性类型有对应关系,详情看下表。 id:数据库主键字段。 result:普通字段。 一对多标签 : collection> property:对应的java属性名 ofType:对应的java属类型 <id property="java属性" column="author_third_id" jdbcType="BIGINT"/> <result property="java属性" column="account_id" jdbcType="VARCHAR"/> --> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" javaType="string" property="name" /> <result column="logo" jdbcType="VARCHAR" property="logo" /> <result column="describe" jdbcType="VARCHAR" property="describe" /> <result column="is_enable" jdbcType="BIT" property="isEnable" /> <result column="phone" jdbcType="VARCHAR" property="phone" /> <result column="admin" jdbcType="VARCHAR" property="admin" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="uuid" jdbcType="VARCHAR" property="uuid" /> </resultMap> <!-- 可以重用的 SQL 块,也可以被其他语句引用。 --> <sql id="Example_Where_Clause"> <where>/* where 可以自动去除sql语句where关键字后的and关键字*/ /* 向sql传递数组或List, mybatis使用foreach解析,可以做批量处理。 collection:传入的集合的变量名称(要遍历的值)。 item:每次循环将循环出的数据放入这个变量中。 open:循环开始拼接的字符串。 close:循环结束拼接的字符串。 separator:循环中拼接的分隔符。 */ <foreach collection="oredCriteria" item="criteria" separator="or"> /* 判断语句,test值等于true执行等于false跳过 test可以是一个值为Boolean型的计算语句 */ <if test="criteria.valid"> /* 前缀'and' 被'(' 替换 prefix:前缀覆盖并增加其内容 不写的话默认替换为空 suffix:后缀覆盖并增加其内容 不写的话默认替换为空 prefixOverrides:前缀判断的条件 suffixOverrides:后缀判断的条件 */ <trim prefix="(" prefixOverrides="and" suffix=")"> <foreach collection="criteria.criteria" item="criterion"> /* choose 是或(or)的关系。 choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。 当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。 类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。 */ <choose> <when test="criterion.noValue"> and ${criterion.condition} </when> <when test="criterion.singleValue"> and ${criterion.condition} #{criterion.value} </when> <when test="criterion.betweenValue"> and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} </when> <when test="criterion.listValue"> and ${criterion.condition} <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=","> #{listItem} </foreach> </when> </choose> </foreach> </trim> </if> </foreach> </where> </sql> <sql id="Update_By_Example_Where_Clause"> <where> <foreach collection="example.oredCriteria" item="criteria" separator="or"> <if test="criteria.valid"> <trim prefix="(" prefixOverrides="and" suffix=")"> <foreach collection="criteria.criteria" item="criterion"> <choose> <when test="criterion.noValue"> and ${criterion.condition} </when> <when test="criterion.singleValue"> and ${criterion.condition} #{criterion.value} </when> <when test="criterion.betweenValue"> and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} </when> <when test="criterion.listValue"> and ${criterion.condition} <foreach close=")" collection="criterion.value" item="listItem" open="(" separator=","> #{listItem} </foreach> </when> </choose> </foreach> </trim> </if> </foreach> </where> </sql> <sql id="Base_Column_List"> <if test="fields == null"> id, name, logo, describe, is_enable, phone, admin, password, uuid </if> <if test="fields != null"> ${fields} </if> </sql> <!-- select查询语句标签 id: 与namespace接口中的方法名对应 parameterType: 参数类型 resultMap : 返回值类型 自增IDset到对象中: useGeneratedKeys="true" keyProperty="id" keyColumn="id" 支持类型简写,详情看下表 --> <select id="selectByExample" useGeneratedKeys="true" keyProperty="id" keyColumn="id" parameterType="cn.rainbowbus.entity.CustomerExample" resultMap="BaseResultMap"> select <if test="distinct"> distinct </if> <include refid="Base_Column_List" />/*引入一个SQL模块*/ from customer <if test="_parameter != null"> <include refid="Example_Where_Clause" /> </if> <if test="orderByClause != null"> order by ${orderByClause} </if> <if test="startRow != null"> limit #{startRow} , #{pageSize} </if> </select> <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap"> select id,name,logo,describe,is_enable,phone,admin,password,uuid from customer where id = #{id,jdbcType=INTEGER} </select> <!-- delete删除语句标签 id: 与namespace接口中的方法名对应 parameterType: 参数类型 --> <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer"> delete from customer where id = #{id,jdbcType=INTEGER} </delete> <delete id="deleteByExample" parameterType="cn.rainbowbus.entity.CustomerExample"> delete from customer <if test="_parameter != null"> <include refid="Example_Where_Clause" /> </if> </delete> <!--插入语句--> <insert id="insert" parameterType="cn.rainbowbus.entity.Customer"> insert into customer (id, name, logo, describe, is_enable, phone, admin, password, uuid ) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}, #{logo,jdbcType=VARCHAR}, #{describe,jdbcType=VARCHAR}, #{isEnable,jdbcType=BIT}, #{phone,jdbcType=VARCHAR}, #{admin,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}, #{uuid,jdbcType=VARCHAR} ) </insert> <insert id="insertSelective" parameterType="cn.rainbowbus.entity.Customer"> insert into customer <trim prefix="(" suffix=")" suffixOverrides=","> <if test="id != null"> id, </if> <if test="name != null"> name, </if> <if test="logo != null"> logo, </if> <if test="describe != null"> describe, </if> <if test="isEnable != null"> is_enable, </if> <if test="phone != null"> phone, </if> <if test="admin != null"> admin, </if> <if test="password != null"> password, </if> <if test="uuid != null"> uuid, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="id != null"> #{id,jdbcType=INTEGER}, </if> <if test="name != null"> #{name,jdbcType=VARCHAR}, </if> <if test="logo != null"> #{logo,jdbcType=VARCHAR}, </if> <if test="describe != null"> #{describe,jdbcType=VARCHAR}, </if> <if test="isEnable != null"> #{isEnable,jdbcType=BIT}, </if> <if test="phone != null"> #{phone,jdbcType=VARCHAR}, </if> <if test="admin != null"> #{admin,jdbcType=VARCHAR}, </if> <if test="password != null"> #{password,jdbcType=VARCHAR}, </if> <if test="uuid != null"> #{uuid,jdbcType=VARCHAR}, </if> </trim> </insert> <select id="countByExample" parameterType="cn.rainbowbus.entity.CustomerExample" resultType="java.lang.Long"> select count(*) from customer <if test="_parameter != null"> <include refid="Example_Where_Clause" /> </if> </select> <update id="updateByExampleSelective" parameterType="map"> update customer <set> <if test="record.id != null"> id = #{record.id,jdbcType=INTEGER}, </if> <if test="record.name != null"> name = #{record.name,jdbcType=VARCHAR}, </if> <if test="record.logo != null"> logo = #{record.logo,jdbcType=VARCHAR}, </if> <if test="record.describe != null"> describe = #{record.describe,jdbcType=VARCHAR}, </if> <if test="record.isEnable != null"> is_enable = #{record.isEnable,jdbcType=BIT}, </if> <if test="record.phone != null"> phone = #{record.phone,jdbcType=VARCHAR}, </if> <if test="record.admin != null"> admin = #{record.admin,jdbcType=VARCHAR}, </if> <if test="record.password != null"> password = #{record.password,jdbcType=VARCHAR}, </if> <if test="record.uuid != null"> uuid = #{record.uuid,jdbcType=VARCHAR}, </if> </set> <if test="_parameter != null"> <include refid="Update_By_Example_Where_Clause" /> </if> </update> <update id="updateByExample" parameterType="map"> update customer set id = #{record.id,jdbcType=INTEGER}, name = #{record.name,jdbcType=VARCHAR}, logo = #{record.logo,jdbcType=VARCHAR}, describe = #{record.describe,jdbcType=VARCHAR}, is_enable = #{record.isEnable,jdbcType=BIT}, phone = #{record.phone,jdbcType=VARCHAR}, admin = #{record.admin,jdbcType=VARCHAR}, password = #{record.password,jdbcType=VARCHAR}, uuid = #{record.uuid,jdbcType=VARCHAR} <if test="_parameter != null"> <include refid="Update_By_Example_Where_Clause" /> </if> </update> <update id="updateByPrimaryKeySelective" parameterType="cn.rainbowbus.entity.Customer"> update customer <set> <if test="name != null"> name = #{name,jdbcType=VARCHAR}, </if> <if test="logo != null"> logo = #{logo,jdbcType=VARCHAR}, </if> <if test="describe != null"> describe = #{describe,jdbcType=VARCHAR}, </if> <if test="isEnable != null"> is_enable = #{isEnable,jdbcType=BIT}, </if> <if test="phone != null"> phone = #{phone,jdbcType=VARCHAR}, </if> <if test="admin != null"> admin = #{admin,jdbcType=VARCHAR}, </if> <if test="password != null"> password = #{password,jdbcType=VARCHAR}, </if> <if test="uuid != null"> uuid = #{uuid,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=INTEGER} </update> <update id="updateByPrimaryKey" parameterType="cn.rainbowbus.entity.Customer"> update customer set name = #{name,jdbcType=VARCHAR}, logo = #{logo,jdbcType=VARCHAR}, describe = #{describe,jdbcType=VARCHAR}, is_enable = #{isEnable,jdbcType=BIT}, phone = #{phone,jdbcType=VARCHAR}, admin = #{admin,jdbcType=VARCHAR}, password = #{password,jdbcType=VARCHAR}, uuid = #{uuid,jdbcType=VARCHAR} where id = #{id,jdbcType=INTEGER} </update> <update id="batchUpdateByKeys" parameterType="java.util.List"> update customer <trim prefix="set" suffixOverrides=","> <trim prefix="id =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.id !=null "> when #{item.id,jdbcType=INTEGER} then #{item.id,jdbcType=INTEGER} </if> <if test="item.id ==null "> when #{item.id,jdbcType=INTEGER} then customer.id </if> </foreach> </trim> <trim prefix="name =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.name !=null "> when #{item.id,jdbcType=INTEGER} then #{item.name,jdbcType=VARCHAR} </if> <if test="item.name ==null "> when #{item.id,jdbcType=INTEGER} then customer.name </if> </foreach> </trim> <trim prefix="logo =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.logo !=null "> when #{item.id,jdbcType=INTEGER} then #{item.logo,jdbcType=VARCHAR} </if> <if test="item.logo ==null "> when #{item.id,jdbcType=INTEGER} then customer.logo </if> </foreach> </trim> <trim prefix="describe =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.describe !=null "> when #{item.id,jdbcType=INTEGER} then #{item.describe,jdbcType=VARCHAR} </if> <if test="item.describe ==null "> when #{item.id,jdbcType=INTEGER} then customer.describe </if> </foreach> </trim> <trim prefix="is_enable =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.isEnable !=null "> when #{item.id,jdbcType=INTEGER} then #{item.isEnable,jdbcType=BIT} </if> <if test="item.isEnable ==null "> when #{item.id,jdbcType=INTEGER} then customer.is_enable </if> </foreach> </trim> <trim prefix="phone =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.phone !=null "> when #{item.id,jdbcType=INTEGER} then #{item.phone,jdbcType=VARCHAR} </if> <if test="item.phone ==null "> when #{item.id,jdbcType=INTEGER} then customer.phone </if> </foreach> </trim> <trim prefix="admin =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.admin !=null "> when #{item.id,jdbcType=INTEGER} then #{item.admin,jdbcType=VARCHAR} </if> <if test="item.admin ==null "> when #{item.id,jdbcType=INTEGER} then customer.admin </if> </foreach> </trim> <trim prefix="password =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.password !=null "> when #{item.id,jdbcType=INTEGER} then #{item.password,jdbcType=VARCHAR} </if> <if test="item.password ==null "> when #{item.id,jdbcType=INTEGER} then customer.password </if> </foreach> </trim> <trim prefix="uuid =case id" suffix="end,"> <foreach collection="recordList" index="index" item="item"> <if test="item.uuid !=null "> when #{item.id,jdbcType=INTEGER} then #{item.uuid,jdbcType=VARCHAR} </if> <if test="item.uuid ==null "> when #{item.id,jdbcType=INTEGER} then customer.uuid </if> </foreach> </trim> </trim> where id in( <foreach collection="recordList" index="index" item="item" separator=","> #{item.id,jdbcType=INTEGER} </foreach> ) </update> <insert id="batchInsert" parameterType="cn.rainbowbus.entity.Customer"> insert into customer (id, name, logo, describe, is_enable, phone, admin, password, uuid) values <foreach collection="list" item="item" index="index" separator="," > (#{item.id,jdbcType=INTEGER}, #{item.name,jdbcType=VARCHAR}, #{item.logo,jdbcType=VARCHAR}, #{item.describe,jdbcType=VARCHAR}, #{item.isEnable,jdbcType=BIT}, #{item.phone,jdbcType=VARCHAR}, #{item.admin,jdbcType=VARCHAR}, #{item.password,jdbcType=VARCHAR}, #{item.uuid,jdbcType=VARCHAR})</foreach> </insert> <delete id="batchDeleteByKeys" parameterType="java.lang.Integer"> delete from customer where id in ( <foreach collection="ids" index="index" item="id" separator=","> #{id} </foreach> ) </delete> </mapper>
注意
#{}占位符: 占位
如果传入的是基本类型,那么#{}中的变量名称可以随意写
如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性.属性名(user.username)
$ {}拼接符: 字符串原样拼接(有sql注入的风险)
如果传入的是基本类型,那么中 的 变 量 名 必 须 是 v a l u e 如 果 传 入 的 参 数 是 p o j o 类 型 , 那 么 {}中的变量名必须是value 如果传入的参数是pojo类型,那么中的变量名必须是value如果传入的参数是pojo类型,那么{}中的变量名称必须是pojo中的属性.属性名(user.username)
注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格
注意:如果是取简单数量类型的参数,括号中的值必须为value
例: select * from user where username like ‘%${value}%'
可以这样写来解决sql注入:select * from user where username like ‘%' #{name}'%'(开发经常使用)
动态SQL
Mybatis提供了9种动态sql标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
1.判断语句if
判断语句,test值等于true执行等于false跳过,test可以是一个值为Boolean型的计算语句
<if test="true"> </if>
2.修剪语句:trim
- 前缀'and' 被'(' 替换
-
prefix
:前缀覆盖并增加其内容 不写的话默认替换为空 -
suffix
:后缀覆盖并增加其内容 不写的话默认替换为空 -
prefixOverrides
:前缀判断的条件 -
suffixOverrides
:后缀判断的条件
<trim prefix="(" suffix=")" prefixOverrides="and" ></trim>
3.循环语句:foreach
- 向sql传递数组或List, mybatis使用foreach解析,可以做批量处理。
-
collection
:传入的集合的变量名称(要遍历的值)。 -
item
:每次循环将循环出的数据放入这个变量中。 -
open
:循环开始拼接的字符串。 -
close
:循环结束拼接的字符串。 -
separator
:循环中拼接的分隔符。
<foreach collection="oredCriteria" item="criteria" separator="or"></foreach>
4.选择语句:choose
类似于Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。
choose 是或(or)的关系。choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的sql。
<choose> <when test="false"> ... </when> <when test="true"> ... </when> <otherwise> ...同样这不是必须的 </otherwise> </choose>
mapper对应的Java接口文件:
package cn.rainbowbus.dao; import cn.rainbowbus.entity.Customer; import cn.rainbowbus.entity.CustomerExample; import java.util.List; import org.apache.ibatis.annotations.Param; public interface CustomerMapper { long countByExample(CustomerExample example); int deleteByExample(CustomerExample example); int deleteByPrimaryKey(Integer id); int insert(Customer record); int insertSelective(Customer record); List<Customer> selectByExample(CustomerExample example); Customer selectByPrimaryKey(Integer id); int updateByExampleSelective(@Param("record") Customer record, @Param("example") CustomerExample example); int updateByExample(@Param("record") Customer record, @Param("example") CustomerExample example); int updateByPrimaryKeySelective(Customer record); int updateByPrimaryKey(Customer record); int batchUpdateByKeys(@Param("recordList") List<Customer> recordList); void batchInsert(List<Customer> recordLst); int batchDeleteByKeys(@Param("ids") Integer[] ids); }
mybatis支持别名:
别名 | 映射类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
map | Map |
jdbcType与JavaType的映射关系
jdbcType | Java Type |
---|---|
CHAR | String |
ARCHAR | String |
ONGVARCHAR | String |
UMERIC | java.math.BigDecimal |
ECIMAL | java.math.BigDecimal |
IT | boolean |
OOLEAN | boolean |
INYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
CLOB | Clob |
BLOB | Blob |
ARRAY | Array |
DISTINCT | mapping of underlying type |
STRUCT | Struct |
REF | Ref |
DATALINK | java.net.URL[color=red][/color] |
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
推荐阅读
-
批处理方法的 myBatis 映射器映射文件
-
Mybatis 聪明地使用 @One 注解一个 SQL 联合查询语句来实现一对一的查询-映射器类。
-
标题:G5400群晖NAS手记(四):DSM7系统新增共享文件夹及常用访问方式-在新增好共享文件夹后,也会将群晖共享文件夹的常用访问使用方法如,win pc的共享网络、磁盘映射、webDAV、手机管理等等一起整理出来;另外要注意file station创建文件夹的区别等等。
-
用批处理方法打造的文件夹对比神器
-
在MyBatis XML映射文件中,如何运用OGNL表达式来调用类中的方法?
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。
-
使用Windows脚本主机(Windows Script Host),创建VBS批处理文件实现Windows自动登录telnet的方法