Mybatis学习整理

Mybatis框架学习总结

一、Mybatis介绍,是什么,有什么特点
Mybatis是一个持久层框架,是apache下的顶级项目,他是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多)
该框架是让程序员将主要的精力放在sql上,通过mybatis提供的映射方式,自由灵活生成满足需要的SQL语句.
mybatis可以将向preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象(输出映射).
ORM(Object Relation Mapping):
对象关系映射,对象指的是java对象,关系指的是数据库中的关系模型,对象关系映射,指的是在java对象和数据库的关系模型之间建立一种对应关系,比如java中的一个实体类去对应数据库中的一张表,实体类中的属性和表中的列具有一一对应的关系。
为什么mybatis是半自动的ORM框架?
用mybatis进行开发,需要手动编写SQL语句,而全自动的ORM框架(hibernate是不需要手动编写SQL语句的),正是因为mybatis需要手写SQL语句,所以他巨有较高的灵活性,可以根据需要,自由的对SQL进行定制,也因为要手写SQL,所以当要切换数据库实,SQL语句可能就要重写,所以mybatis的数据库无关性低,虽然mybatis需要手写SQL,但相对JDBC来说,它提供了输入映射和输出映射,可以很方便的进行SQL参数设置,以及结果集封装,而且还提供了关联查询和动态SQL等功能,极大的提升了开发的效率,且学习成本要比Hibernate低.
二、快速入门
当然,你只需要如下几个步骤,即可用mybatis快速进行持久层的开发
1、编写全局配置文件
2、编写mapper映射文件
3、加载全局配置文件,生成SqlSessionFactory
4、创建SqlSession,调用mapper映射文件中的SQL语句来执行CRUD操作

		1.1:入门配置:在web过程中,对于Mybatis最核心的全局配置文件时mybatis-configration.xml文件,其中包含了数据库的连接配置信息、Mapper映射文件的加载路径、全局参数、类型别名等。
<?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">
<!-- 包裹所有配置标签,是整个配置文件的顶级标签 -->
<configuration>
  <!-- properties属性引入外部配置文件 -->
  <properties resource="mybatis.properties"></properties>
  <!-- environments数据库环境配置 -->
<!-- 和Spring整合后environments配置将被废除 -->
  <environments default="development">
	<environment id="development">
    <!-- 使用JDBC事物管理 -->
      <transactionManager type="JDBC"/>
		<!-- 数据库连接池 -->
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
	<!-- 加载映射文件 -->
  <mappers>
    <mapper resource="cn/com/xyp/mapper/testInfoMapper.xml"/>
  </mappers>
</configuration>
配置标签简介:

<1>、configuration:包裹所有的配置标签,是整个配置文件的顶级标签

<2>、properties:属性,改标签可以引入外部配置的属性,也可以自己配置,该配置标签所在的同一个配置文件中的其他配置均可引用此配置中的属性,properties 标签中的配置可以供整个配置文件使用,在任何位置都可以引入其中配置的值。properties 标签可以通过子标签 property 标签来配置一些子元素信息,也可以配置外部的动态文件。设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。
如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
首先读取在 properties 元素体内指定的属性。
然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

<properties resource="org/mybatis/example/config.properties">
	<property name="username" value="dev_user"/>
	<property name="password" value="F2Fa3!33TYyg"/>
</properties>

<3>、setting:全局配置参数,用来配置一些改变运行时行为的消息,例如是否使用缓存机制,是否使用延迟加载,是否使用错误处理机制等,并且可以设置最大并发请求数量,最大并发事务数量,以及是否启用命令空间等.
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。详见官网配置表。

<settings>
  		<!-- 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 -->
  <setting name="cacheEnabled" value="true"/>
  		<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 -->
  <setting name="lazyLoadingEnabled" value="true"/>
		  <!-- 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 			-->
		  <!-- 是否允许单个语句返回多结果集(需要数据库驱动支持)。 -->
<setting name="multipleResultSetsEnabled" value="true"/>
		<!-- 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 -->
  <setting name="useColumnLabel" value="true"/>
		<!-- 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby) -->
  <setting name="useGeneratedKeys" value="false"/>
		<!-- 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 -->
  <setting name="autoMappingBehavior" value="PARTIAL"/>
		  <!-- 指定发现自动映射目标未知列(或未知属性类型)的行为。
		NONE: 不做任何反应
		WARNING: 输出警告日志
		FAILING: 映射失败 (抛出 SqlSessionException)
		  -->
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
		<!-- 配置默认的执行器。
		SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 -->
  <setting name="defaultExecutorType" value="SIMPLE"/>
		<!-- 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 -->
  <setting name="defaultStatementTimeout" value="25"/>
		<!-- 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 -->
  <setting name="defaultFetchSize" value="100"/>
 		<!-- 是否允许在嵌套语句中使用分页(RowBounds)-->
  <setting name="safeRowBoundsEnabled" value="false"/>
		<!-- 是否开启驼峰命名自动映射-->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
		<!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。-->
  <setting name="localCacheScope" value="SESSION"/>
		<!-- 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,		比如 NULL、VARCHAR 或 OTHER。-->
  <setting name="jdbcTypeForNull" value="OTHER"/>
		<!-- 指定对象的哪些方法触发一次延迟加载。-->
  <setting name="lazyLoadTriggerMethods"value="equals,clone,hashCode,toString"/>
</settings>

<4>、typeAlises:类型别名,用来设置一些别名来代替java的长类型声明,eg:java.lang.int变为int,减少配置编码的冗余
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<!--
alias:定义的别名
type:指定要起别名的类型全类名
默认别名就是类名小写
通过包扫描的方式
该方式将扫描该包下的所有类,默认为类名。
-->
<typeAliases>
		<typeAlias type="cn.com.xyp.pojo.testInfo" alias="testInfo"/> 
		<package name="cn.com.xyp.pojo"/>
</typeAliases>
			每一个在包domain.blog中的java Bean在没有注解的情况下,会使用Bean的首字母小写的非限定类名来作为它的别名,比如domain.blog.Author的别名为author;若是有注解,则别名为其注解

typeHandlers:类型处理器,将 sql 中返回的数据库类型转换为相应 Java 类型的处理器配置
			MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。

<5>、objectFactory(对象工厂)
每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。

<6>、environments:环境集合属性对象,数据库环境信息的集合。在一个配置文件中,可以有多种数据库环境集合,这样使 MyBatis 将 sql 同时映射至多个数据库
MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例
每个数据库对应一个 SqlSessionFactory 实例
为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。可以接受环境配置的两个方法签名是:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
如果忽略了环境参数,那么将会加载默认环境,如下所示:
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
<!-- 默认环境 默认环境ID-->
<environments default="development">
    <!--配置环境 环境ID -->
		<environment id="development">
		      <!-- 事务管理 -->
		      <transactionManager type="JDBC"/>
		      <!-- 数据源 -->
		      <dataSource type="POOLED">
			        <property name="driver" value="${driver}"/>
			        <property name="url" value="${url}"/>
			        <property name="username" value="${username}"/>
			        <property name="password" value="${password}"/>
		      </dataSource>
		</environment>
  </environments>
		默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

<7>、environment:环境子属性对象,数据库环境配置的详细配置。

<8>、transactionManager:事务管理,指定 MyBatis 的事务管理器

<9>、dataSource:数据源,使其中的 type 指定数据源的连接类型,在标签对中可以使用 property 属性指定数据库连接池的其他信息
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]")

<10>、databaseldProvicer(数据库厂商标识)
Mybatis可以根据不同的数据库厂商执行不同的SQL语句,这种支持多厂商是基于映射语句中的databaseId属性
的语句,Mybatis会加载带有匹配当前数据库databaseId属性和所有不带databaseId属性的语句,如果同时找到
带有databaseId和不带databaseId的相同语句,则或者将会被舍弃,为支持多厂商特性,只要向这样在
mybatis-config.xml文件中加入databaseIdProvider即可

<databaseIdProvider type="DB_VENDOR" />

<11>、mappers:映射器,配置 sql 映射文件的位置,告知 MyBatis 去哪里加载 sql 映射配置。
既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

三、xxxMapper.xml映射文件
**3.1、**入门配置
sql映射文件只有很少的几个顶级元素,如下:
cache——该命名空间的缓存配置
cache-ref——引用其他命名空间的缓存配置
resultMap——描述如何将从数据库结果集中加载对象,是最复杂也是最强大的元素
sql——可被其他语句引用的可重用的语句块
insert——映射插入语句
update——映射更新语句
delete——映射删除语句
select——映射查询语句

<?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="cn.com.yk.mapper.UserMapper">
	
</mapper>

3.2、insert、updata、delete、select语句

<select id="selectPerson" parameterType="int" resultType="hashmap">
  	SELECT * FROM PERSON WHERE ID = #{id}
</select>

<insert id="insertAuthor">
	  insert into Author (id,username,password,email,bio)
	  values (#{id},#{username},#{password},#{email},#{bio})
</insert>

<update id="updateAuthor">
	  update Author set
	    username = #{username},
	    password = #{password},
	    email = #{email},
	    bio = #{bio}
	  where id = #{id}
</update>

<delete id="deleteAuthor">
  	delete from Author where id = #{id}
</delete>

3.3、insert、update、delete、select元素共同拥有的属性
id:
在命名空间中唯一的标识符,可以被用来引用这条语句。

parameterType
将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。

flushCache
将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。

timeout
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)

databaseId
如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略

statementType
可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。

不同属性:
1、insert、update元素拥有的属性
1.1、useGeneratedKeys:(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
1.2、keyProperty:(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
1.3、keyColumn:(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
2、select元素拥有的属性
2.1、resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
2.2、resultMap 对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
2.3、useCache 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
2.4、fetchSize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
2.5、resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。
2.6、resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
注意
在使用mybatis时我们SQL是写在.xml映射文件中,如果写的SQL中有一些特殊的字符的话,在解析xml文件的时候会被转义,但我们不希望他被转义,所以这时我们就需要使用<![CDATA[]]>来解决,<![CDATA[]]>是xml的语法,在CDATA内部的所有的内容都会被解析器忽略,如果文本包含了很多的"<"字符 <=和“&”字符——就像程序代码一样,那么最好将他们都放到CDATA部件中,但是有一个问题就是 等这些标签都不会被解析,所有我们只把有特殊字符的语句放在<![CDATA[ ]]>尽量缩小<![CDATA[]]>的范围

四、测试类

public class TestUser {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            String resource = "config/mybatis-configration.xml";
            // 加载mybatis-config.xml核心配置文件(存放在类路径下 classpath)
            InputStream in = Resources.getResourceAsStream(resource);
             // 使用构建者类SqlSessionFactoryBuilder构建SqlSessionFactory工厂对象
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
            // 通过工厂对象创建SqlSession对象
            SqlSession session = factory.openSession(true);
            // 使用sqlSession对象创建UserMapper对应的代理对象
            UserMapper mapper = session.getMapper(UserMapper.class);
            /**查询姓张的用户*/
            //TestUser.select(mapper);
            /**批量3条数据*/
            //TestUser.insertList(mapper);
            /**删除id为2的用户*/
            //TestUser.delete(mapper);
            /**如果sex为空,将性别修改为女,如果age小于28则将年龄修改为28*/
            TestUser.updateNewSql(mapper);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

    }

    /**
    * 查询含有张字用户的数据
    * */
    public static void select(UserMapper mapper){
    
    
        List<User> value = mapper.selectUser();
        for (User user:value){
    
    
            System.out.println(user);
        }
    }


    /**批量插入三条数据*/
    public static void insertList(UserMapper mapper){
    
    
        List<User> list = new ArrayList<>();
        User u = new User();
        u.setName("于和伟");
        u.setSex("男");
        u.setAge(22);
        list.add(u);

        User u2 = new User();
        u2.setName("边国丽");
        u2.setSex("男");
        u2.setAge(22);
        list.add(u2);

        User u3 = new User();
        u3.setName("曹舒浩");
        u3.setSex("男");
        u3.setAge(23);
        list.add(u3);
        String value =mapper.insertList(list)>0?"数据插入成功!":"数据插入失败!";
        System.out.println(value);
    }


    /**删除id为2的数据信息*/
    public static void delete(UserMapper mapper){
    
    
        String value = mapper.delete(2)>0?"数据删除成功":"数据删除失败!";
        System.out.println(value);
    }

    /**修改为性别为null和age小于28岁的用户数据*/
    public static void updateNewSql(UserMapper mapper){
    
    
        int value = mapper.updateNewSql();
        System.out.println(value);
    }
}

Mapper接口

public interface UserMapper {
    
    

    /**
     * 批量插入数据
     * @return int
     * @param user
     * */
    int insertList(List<User> user);

    /**
     * 根据id删除数据
    * @return int
     * @param id
    * */
    int delete(int id);

    /**
     * 查询语句
     * @return list
     * */
    List<User> selectUser();

    /**
     * 更新语句
     * @return int*/
    int updateNewSql();
}

xxxMapper.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="cn.mapper.UserMapper">

    <!--模糊查询所有含有张字的用户数据-->
    <select id="selectUser" resultType="cn.pojo.User">
    SELECT * FROM USER3 WHERE NAME LIKE '%张%'
    </select>

    <!--list集合插入数据-->
    <insert id="insertList" keyColumn="id" keyProperty="id">
        insert into user3(id,name,sex,age) values
        <foreach collection="list" item="item" separator=",">
            (null ,#{item.name},#{item.sex},#{item.age})
        </foreach>
    </insert>

    <!--删除数据id为2的数据-->
    <delete id="delete" parameterType="int">
        delete from user3 where id=#{arg0}
    </delete>

    <!--修改为性别为null和age小于28岁的用户数据-->
    <update id="updateNewSql">
        <![CDATA[update user3 set sex="女",age=28 where isnull(sex) or age<28]]>
    </update>
</mapper>

补充:
**#{}和KaTeX parse error: Expected 'EOF', got '#' at position 14: {}的区别:** 1、#̲{} 表示一个占位符 ?. …{} 表示字符串的拼接 大多数使用在 输出sql字段名
7、使用 可 以 将 p a r a m e t e T y p e 传 入 的 内 容 拼 接 在 s q l 中 , 不 进 行 类 型 的 转 换 8 、 {}可以将parameteType传入的内容拼接在sql中 ,不进行类型的转换 8、 parameteTypesql8{} 可以接收简单类型值或者pojo属性值
9、如果parameteType传输单个简单类型值,${}括号中的只能value
mapper.xml中使用比较运算符需要进行转义:
在sql语句中日期可以直接进行大小比较,使用比较运算符 >= <= > < = !=
>——表示大于号
<——表示小于号
eg:select * from user where birthday > “2021-3-17”
select * from user where <![CDATA[ birthday < "2021-3-17" ]]>

猜你喜欢

转载自blog.csdn.net/weixin_44706803/article/details/114898551
今日推荐