MybatisPlus-持久层框架
本文最后更新于22 天前,其中的信息可能已经过时,如有错误请发送邮件到qiqin-chang@qq.com

基础信息:

官方地址:https://www.baomidou.com/

依赖:

SpringBoot2:

<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter</artifactId>
   <version>3.5.10.1</version>
</dependency>

SpringBoot3:

<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
   <version>3.5.10.1</version>
</dependency>

测试依赖:

<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter-test</artifactId>
   <version>3.5.10.1</version>
</dependency>

配置:

常见配置:

说明:可不配

mybatis-plus:
type-aliases-package: com.chang.*.*.pojo #实体类位置(改成自己的实体类位置)
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml文件的存放地址(当前这个是默认值)。
 
global-config:
  db-config:
    id-type: auto # 全局id类型为自增长  

XML文件模版:

<?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.chang.mapper.UserMapper">
<!--内容-->
</mapper>

动态XML:

    <foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
    <if test="status != null">
      status = #{status}
</if>

定义Mapper

继承BaseMapper

实现单表CRUD

public interface UserMapper extends BaseMapper<User> {
}

单元测试:

说明:单表增删改查

@SpringBootTest
class UserMapperTest {

   @Autowired
   private UserMapper userMapper;

   @Test
   void testInsert() {
       User user = new User();
       user.setId(5L);
       user.setUsername("Lucy");
       user.setPassword("123");
       user.setPhone("18688990011");
       user.setBalance(200);
       user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
       user.setCreateTime(LocalDateTime.now());
       user.setUpdateTime(LocalDateTime.now());
       userMapper.insert(user);
  }

   @Test
   void testSelectById() {
       User user = userMapper.selectById(5L);
       System.out.println("user = " + user);
  }

   @Test
   void testSelectByIds() {
       List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L, 5L));
       users.forEach(System.out::println);
  }

   @Test
   void testUpdateById() {
       User user = new User();
       user.setId(5L);
       user.setBalance(20000);
       userMapper.updateById(user);
  }

   @Test
   void testDelete() {
       userMapper.deleteById(5L);
  }
}

实体注解:

@TableName

说明:

  • 描述:表名注解,标识实体类对应的表
  • 使用位置:实体类
属性类型必须指定默认值描述
valueString“”表名
schemaString“”schema
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时)
resultMapString“”xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定)
autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入)
excludePropertyString[]{}需要排除的属性名 @since 3.3.1

@TableId

说明:

  • 描述:主键注解,标识实体类中的主键字段
  • 使用位置:实体类的主键字段
属性类型必须指定默认值描述
valueString“”表名
typeEnumIdType.NONE指定主键类型

IdType类型有:

描述
AUTO数据库 ID 自增
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert 前手动生成主键值
ASSIGN_ID分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法)

@TableField

说明:

  • 成员变量名与数据库字段名不一致
  • 成员变量是以isXXX命名,按照JavaBean的规范,MybatisPlus识别字段时会把is去除,这就导致与数据库不符。
  • 成员变量名与数据库一致,但是与数据库的关键字冲突。使用@TableField注解给字段名添加转义字符:““
属性类型必填默认值描述
valueString“”数据库字段名
existbooleantrue是否为数据库表字段
conditionString“”字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考(opens new window)
updateString“”字段 update set 部分注入,例如:当在version字段上注解update=”%s+1″ 表示更新时会 set version=version+1 (该属性优先级高于 el 属性)
insertStrategyEnumFieldStrategy.DEFAULT举例:NOT_NULL insert into table_a(<if test=”columnProperty != null”>column</if>) values (<if test=”columnProperty != null”>#{columnProperty}</if>)
updateStrategyEnumFieldStrategy.DEFAULT举例:IGNORED update table_a set column=#{columnProperty}
whereStrategyEnumFieldStrategy.DEFAULT举例:NOT_EMPTY where <if test=”columnProperty != null and columnProperty!=””>column=#{columnProperty}</if>
fillEnumFieldFill.DEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理
jdbcTypeJdbcTypeJdbcType.UNDEFINEDJDBC 类型 (该默认值不代表会按照该值生效)
typeHandlerTypeHander类型处理器 (该默认值不代表会按照该值生效)
numericScaleString“”指定小数点后保留的位数

核心功能:

条件构造器:

构筑复杂的where条件。

参数Wrapper是条件构造的抽象类,继承关系如图:

Wrapper的子类AbstractWrapper提供了where中包含的所有条件构造方法:

而QueryWrapper在AbstractWrapper的基础上拓展了一个select方法,允许指定查询字段:

而UpdateWrapper在AbstractWrapper的基础上拓展了一个set方法,允许指定SQL中的SET部分:

QueryWrapper

构建查询条件:

查询:查询出名字中带o的,存款大于等于1000元的人。代码如下:

@Test
void testQueryWrapper() {
   // 1.构建查询条件 where name like "%o%" AND balance >= 1000
   QueryWrapper<User> wrapper = new QueryWrapper<User>()
          .select("id", "username", "info", "balance")
          .like("username", "o")
          .ge("balance", 1000);
   // 2.查询数据
   List<User> users = userMapper.selectList(wrapper);
   users.forEach(System.out::println);
}

更新:更新用户名为jack的用户的余额为2000,代码如下:

@Test
void testUpdateByQueryWrapper() {
   // 1.构建查询条件 where name = "Jack"
   QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "Jack");
   // 2.更新数据,user中非null字段都会作为set语句
   User user = new User();
   user.setBalance(2000);
   userMapper.update(user, wrapper);
}

UpdateWrapper:

构建更新语句:基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。

更新:id为1,2,4的用户的余额,扣200

@Test
void testUpdateWrapper() {
   List<Long> ids = List.of(1L, 2L, 4L);
   // 1.生成SQL
   UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
          .setSql("balance = balance - 200") // SET balance = balance - 200
          .in("id", ids); // WHERE id in (1, 2, 4)
       // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据,
   // 而是基于UpdateWrapper中的setSQL来更新
   userMapper.update(null, wrapper);
}

LambdaQueryWrapper:

基于变量的gettter方法结合反射技术。我们只要将条件对应的字段的getter方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的方法引用和Lambda表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:

  • LambdaQueryWrapper
  • LambdaUpdateWrapper

分别对应QueryWrapper和UpdateWrapper

其使用方式如下:

@Test
void testLambdaQueryWrapper() {
   // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
   QueryWrapper<User> wrapper = new QueryWrapper<>();
   wrapper.lambda()
          .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
          .like(User::getUsername, "o")
          .ge(User::getBalance, 1000);
   // 2.查询
   List<User> users = userMapper.selectList(wrapper);
   users.forEach(System.out::println);
}

自定义SQL:

MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL

基本用法:

实例如下:

@Test
void testCustomWrapper() {
   // 1.准备自定义查询条件
   List<Long> ids = List.of(1L, 2L, 4L);
   QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

   // 2.调用mapper的自定义方法,直接传递Wrapper
   userMapper.deductBalanceByIds(200, wrapper);
}

在UserMapper中自定义SQL:

public interface UserMapper extends BaseMapper<User> {
   @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
   void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}

多表关联:

理论上来讲MyBatisPlus是不支持多表查询的,我们可以利用Wrapper中自定义条件结合自定义SQL来实现多表查询的效果。

例如,要查询出所有收货地址在北京的并且用户id在1、2、4之中的用户 要是自己基于mybatis实现SQL:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
    SELECT *
    FROM user u
    INNER JOIN address a ON u.id = a.user_id
    WHERE u.id
     <foreach collection="ids" separator="," item="id" open="IN (" close=")">
        #{id}
     </foreach>
    AND a.city = #{city}
 </select>

基于自定义SQL与Wrappe的结合,利用Wrapper来构建查询条件,手写SELECT及FROM部分,实现多表查询。

查询条件构建:

@Test
void testCustomJoinWrapper() {
   // 1.准备自定义查询条件
   QueryWrapper<User> wrapper = new QueryWrapper<User>()
          .in("u.id", List.of(1L, 2L, 4L))
          .eq("a.city", "北京");

   // 2.调用mapper的自定义方法
   List<User> users = userMapper.queryUserByWrapper(wrapper);

   users.forEach(System.out::println);
}

然后在UserMapper中自定义方法:

@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);

或在UserMapper.xml中写SQL:

<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
  SELECT * FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}
</select>

Service接口:

MybatisPlus提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法分为以下几类:

  • save:新增
  • remove:删除
  • update:更新
  • get:查询单个结果
  • list:查询集合结果
  • count:计数
  • page:分页查询

CRUD:

基本的CRUD接口:

新增:

  • save是新增单个元素
  • saveBatch是批量新增
  • saveOrUpdate是根据id判断,如果数据存在就更新,不存在则新增
  • saveOrUpdateBatch是批量的新增或修改

删除:

  • removeById:根据id删除
  • removeByIds:根据id批量删除
  • removeByMap:根据Map中的键值对为条件删除
  • remove(Wrapper<T>):根据Wrapper条件删除
  • ~~removeBatchByIds~~:暂不支持

修改:

  • updateById:根据id修改
  • update(Wrapper<T>):根据UpdateWrapper修改,Wrapper中包含setwhere部分
  • update(T,Wrapper<T>):按照T内的数据修改与Wrapper匹配到的数据
  • updateBatchById:根据id批量修改

Get:

  • getById:根据id查询1条数据
  • getOne(Wrapper<T>):根据Wrapper查询1条数据
  • getBaseMapper:获取Service内的BaseMapper实现,某些时候需要直接调用Mapper内的自定义SQL时可以用这个方法获取到Mapper

List:

  • listByIds:根据id批量查询
  • list(Wrapper<T>):根据Wrapper条件查询多条数据
  • list():查询所有

Count:

  • count():统计所有数量
  • count(Wrapper<T>):统计符合Wrapper条件的数据数量

getBaseMapper: 当我们在service中要调用Mapper中自定义SQL时,就必须获取service对应的Mapper,就可以通过这个方法:

基本用法:

由于Service中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService,而是自定义Service接口,然后继承IService以拓展方法。同时,让自定义的Service实现类继承ServiceImpl,这样就不用自己实现IService中的接口了。

首先,定义IUserService,继承IService

public interface IUserService extends IService<User> {
   // 拓展自定义方法
}

然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}

接下来,我们快速实现下面4个接口:

编号接口请求方式请求路径请求参数返回值
1新增用户POST/users用户表单实体
2删除用户DELETE/users/{id}用户id
3根据id查询用户GET/users/{id}用户id用户VO
4根据id批量查询GET/users用户id集合用户VO集合

然后,接口需要两个实体:

  • UserFormDTO:代表新增时的用户表单
  • UserVO:代表查询的返回结果

首先是UserFormDTO:

@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {

   @ApiModelProperty("id")
   private Long id;

   @ApiModelProperty("用户名")
   private String username;

   @ApiModelProperty("密码")
   private String password;

   @ApiModelProperty("注册手机号")
   private String phone;

   @ApiModelProperty("详细信息,JSON风格")
   private String info;

   @ApiModelProperty("账户余额")
   private Integer balance;
}

然后是UserVO:

@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
   
   @ApiModelProperty("用户id")
   private Long id;
   
   @ApiModelProperty("用户名")
   private String username;
   
   @ApiModelProperty("详细信息")
   private String info;

   @ApiModelProperty("使用状态(1正常 2冻结)")
   private Integer status;
   
   @ApiModelProperty("账户余额")
   private Integer balance;
}

最后,按照Restful风格编写Controller接口方法:

@Api(tags = "用户管理接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("users")
public class UserController {

   private final IUserService userService;

   @PostMapping
   @ApiOperation("新增用户")
   public void saveUser(@RequestBody UserFormDTO userFormDTO){
       // 1.转换DTO为PO
       User user = BeanUtil.copyProperties(userFormDTO, User.class);
       // 2.新增
       userService.save(user);
  }

   @DeleteMapping("/{id}")
   @ApiOperation("删除用户")
   public void removeUserById(@PathVariable("id") Long userId){
       userService.removeById(userId);
  }

   @GetMapping("/{id}")
   @ApiOperation("根据id查询用户")
   public UserVO queryUserById(@PathVariable("id") Long userId){
       // 1.查询用户
       User user = userService.getById(userId);
       // 2.处理vo
       return BeanUtil.copyProperties(user, UserVO.class);
  }

   @GetMapping
   @ApiOperation("根据id集合查询用户")
   public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){
       // 1.查询用户
       List<User> users = userService.listByIds(ids);
       // 2.处理vo
       return BeanUtil.copyToList(users, UserVO.class);
  }
}

可以看到上述接口都直接在controller即可实现,无需编写任何service代码,非常方便。

不过,一些带有业务逻辑的接口则需要在service中自定义实现了。例如下面的需求:

  • 根据id扣减用户余额

这看起来是个简单修改功能,只要修改用户余额即可。但这个业务包含一些业务逻辑处理:

  • 判断用户状态是否正常
  • 判断用户余额是否充足

这些业务逻辑都要在service层来做,另外更新余额需要自定义SQL,要在mapper中来实现。因此,我们除了要编写controller以外,具体的业务还要在service和mapper中编写。

首先在UserController中定义一个方法:

@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣减用户余额")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
   userService.deductBalance(id, money);
}

然后是UserService接口:

public interface IUserService extends IService<User> {
   void deductBalance(Long id, Integer money);
}

最后是UserServiceImpl实现类:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
   @Override
   public void deductBalance(Long id, Integer money) {
       // 1.查询用户
       User user = getById(id);
       // 2.判断用户状态
       if (user == null || user.getStatus() == 2) {
           throw new RuntimeException("用户状态异常");
      }
       // 3.判断用户余额
       if (user.getBalance() < money) {
           throw new RuntimeException("用户余额不足");
      }
       // 4.扣减余额
       baseMapper.deductMoneyById(id, money);
  }
}

最后是mapper:

@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);

Lambda:

IService中提供了Lambda功能来简化复杂查询及更新功能:

案例一:实现一个根据复杂条件查询用户的接口,查询条件如下:

  • name:用户名关键字,可以为空
  • status:用户状态,可以为空
  • minBalance:最小余额,可以为空
  • maxBalance:最大余额,可以为空

可以理解成一个用户的后台管理界面,管理员可以自己选择条件来筛选用户,因此上述条件不一定存在,需要做判断。

我们首先需要定义一个查询条件实体,UserQuery实体:

package com.itheima.mp.domain.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
   @ApiModelProperty("用户名关键字")
   private String name;
   @ApiModelProperty("用户状态:1-正常,2-冻结")
   private Integer status;
   @ApiModelProperty("余额最小值")
   private Integer minBalance;
   @ApiModelProperty("余额最大值")
   private Integer maxBalance;
}

接下来我们在UserController中定义一个controller方法:

@GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){
   // 1.组织条件
   String username = query.getName();
   Integer status = query.getStatus();
   Integer minBalance = query.getMinBalance();
   Integer maxBalance = query.getMaxBalance();
   LambdaQueryWrapper<User> wrapper = new QueryWrapper<User>().lambda()
          .like(username != null, User::getUsername, username)
          .eq(status != null, User::getStatus, status)
          .ge(minBalance != null, User::getBalance, minBalance)
          .le(maxBalance != null, User::getBalance, maxBalance);
   // 2.查询用户
   List<User> users = userService.list(wrapper);
   // 3.处理vo
   return BeanUtil.copyToList(users, UserVO.class);
}

在组织查询条件的时候加入username != null这样的参数,意思就是当条件成立时才会添加这个查询条件

不过,上述条件构建的代码太麻烦了。 因此Service中对LambdaQueryWrapperLambdaUpdateWrapper的用法进一步做了简化。我们无需自己通过new的方式来创建Wrapper,而是直接调用lambdaQuerylambdaUpdate方法:

基于Lambda查询:

@GetMapping("/list")
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUsers(UserQuery query){
   // 1.组织条件
   String username = query.getName();
   Integer status = query.getStatus();
   Integer minBalance = query.getMinBalance();
   Integer maxBalance = query.getMaxBalance();
   // 2.查询用户
   List<User> users = userService.lambdaQuery()
          .like(username != null, User::getUsername, username)
          .eq(status != null, User::getStatus, status)
          .ge(minBalance != null, User::getBalance, minBalance)
          .le(maxBalance != null, User::getBalance, maxBalance)
          .list();
   // 3.处理vo
   return BeanUtil.copyToList(users, UserVO.class);
}

可以发现lambdaQuery方法中除了可以构建条件,还需要在链式编程的最后添加一个list(),这是在告诉MP我们的调用结果需要是一个list集合。这里不仅可以用list(),可选的方法有:

  • .one():最多1个结果
  • .list():返回集合结果
  • .count():返回计数结果

MybatisPlus会根据链式编程的最后一个方法来判断最终的返回结果。

与lambdaQuery方法类似,IService中的lambdaUpdate方法可以非常方便的实现复杂更新业务。

例如下面的需求:

需求:改造根据id修改用户余额的接口,要求如下

  • 如果扣减后余额为0,则将用户status修改为冻结状态(2)

也就是说我们在扣减用户余额时,需要对用户剩余余额做出判断,如果发现剩余余额为0,则应该将status修改为2,这就是说update语句的set部分是动态的。

实现如下:

@Override
@Transactional
public void deductBalance(Long id, Integer money) {
   // 1.查询用户
   User user = getById(id);
   // 2.校验用户状态
   if (user == null || user.getStatus() == 2) {
       throw new RuntimeException("用户状态异常!");
  }
   // 3.校验余额是否充足
   if (user.getBalance() < money) {
       throw new RuntimeException("用户余额不足!");
  }
   // 4.扣减余额 update tb_user set balance = balance - ?
   int remainBalance = user.getBalance() - money;
   lambdaUpdate()
          .set(User::getBalance, remainBalance) // 更新余额
          .set(remainBalance == 0, User::getStatus, 2) // 动态判断,是否更新status
          .eq(User::getId, id)
          .eq(User::getBalance, user.getBalance()) // 乐观锁
          .update();
}

批量新增:

IService中批量新增功能使用:

MybatisPlus批量处理:

@Test
void testSaveBatch() {
   // 准备10万条数据
   List<User> list = new ArrayList<>(1000);
   long b = System.currentTimeMillis();
   for (int i = 1; i <= 100000; i++) {
       list.add(buildUser(i));
       // 每1000条批量插入一次
       if (i % 1000 == 0) {
           userService.saveBatch(list);
           list.clear();
      }
  }
   long e = System.currentTimeMillis();
   System.out.println("耗时:" + (e - b));
}

private User buildUser(int i) {
   User user = new User();
   user.setUsername("user_" + i);
   user.setPassword("123");
   user.setPhone("" + (18688190000L + i));
   user.setBalance(2000);
   user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
   user.setCreateTime(LocalDateTime.now());
   user.setUpdateTime(user.getCreateTime());
   return user;
}

要得到最佳性能,需将多条SQL合并为一条,如下所示:

INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);

做法:

在MySQL的客户端连接参数中有这样的一个参数:rewriteBatchedStatements。是重写批处理的statement语句。这个参数的默认值是false,我们需要修改连接参数,将其配置为true

修改项目中的application.yml文件,在jdbc的url后面添加参数

&rewriteBatchedStatements=true
spring:
datasource:
  url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true

返回顶部

暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇