zoukankan      html  css  js  c++  java
  • mybatis

    1.概述

    使用mybatis的主要Java接口就是SqlSession。可以通过这个接口来执行命令,获取映射器和事务管理。

    SqlSession是由SqlSessionFactory实例创建,SqlSessionFactory对象包含创建SqlSession实例的所有方法。而SqlSessionFactory本身是由SqlSessionFactoryBuilder创建的,可以从XML,注解或手动配置Javadiam来创建SqlSessionFactory。

    2. SqlSessionFactoryBuilder

    有5个build()方法,每一种都允许你从不同的资源中创建一个SqlSession实例。

    SqlSessionFactory build(InputStream inputStream)
    SqlSessionFactory build(InputStream inputStream, String environment)
    SqlSessionFactory build(InputStream inputStream, Properties properties)
    SqlSessionFactory build(InputStream inputStream, String env, Properties props)
    SqlSessionFactory build(Configuration config)
    

    第一种方法是最常用的,它使用了一个参照了XML文档或上面讨论过的更特定的mybatis-config.xml文件的 Reader实例。可选的参数是environment和properties。environment决定加载哪种环境,包括数据源和事务管理器。  

    如果你调用了参数有environment的build 方法,那么MyBatis将会使用configuration对象来配置这个 environment。当然,如果你指定了一个不合法的environment,你就会得到错误提示。如果你调用了不带 environment参数的build方法,那么就使用默认的environment(在上面的示例中指定为default="development" 的代码)。

    如果你调用了参数有properties实例的方法,那么MyBatis就会加载那些properties(属性配置文件),并在配置中可用。那些属性可以用${propName}语法形式多次用在配置文件中。

    总结一下,前四个方法很大程度上是相同的,但是由于覆盖机制,便允许你可选地指定 environment 和/或 properties。以下给出一个从 mybatis-config.xml 文件创建 SqlSessionFactory 的示例:

    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(inputStream);
    

    Resources工具类,这个类在org.apache.ibatis.io包里面。可以从类路径,文件系统或一个web URL中加载资源文件。  

    Configuration类包含可能需要了解SqlSessionFactory实例的所有内容。这里有一个简单的示例,教你如何手动配置configuration实例,然后将它传递给build()方法来创建SqlSessionFactory。

    DataSource dataSource = BaseDataTest.createBlogDataSource();
    TransactionFactory transactionFactory = new JdbcTransactionFactory();
    
    Environment environment = new Environment("development", transactionFactory, dataSource);
    
    Configuration configuration = new Configuration(environment);
    configuration.setLazyLoadingEnabled(true);
    configuration.setEnhancementEnabled(true);
    configuration.getTypeAliasRegistry().registerAlias(Blog.class);
    configuration.getTypeAliasRegistry().registerAlias(Post.class);
    configuration.getTypeAliasRegistry().registerAlias(Author.class);
    configuration.addMapper(BoundBlogMapper.class);
    configuration.addMapper(BoundAuthorMapper.class);
    
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    SqlSessionFactory factory = builder.build(configuration);
    

    3. SqlSessionFactory

    SqlSessionFactory有6个方法创建SqlSession实例。通常来说,当你选择这些方法时你需要考虑以下几点:

    • 事务处理:需要在session使用事务或者使用自动提交功能?
    • 连接:需要依赖mybatis获得来自数据源的配置?还是使用自己提供的配置?
    • 执行语句:需要mybatis复用预处理语句,还是批量更新语句?

    API:

    SqlSession openSession()
    SqlSession openSession(boolean autoCommit)
    SqlSession openSession(Connection connection)
    SqlSession openSession(TransactionIsolationLevel level)
    SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
    SqlSession openSession(ExecutorType execType)
    SqlSession openSession(ExecutorType execType, boolean autoCommit)
    SqlSession openSession(ExecutorType execType, Connection connection)
    Configuration getConfiguration();  

    默认的openSession()方法没有参数,它会创建有如下特性的SqlSession:

    • 会开启一个事务(也就是不自动提交);
    • 将从由当前环境配置的DataSource实例中获取Connection对象;
    • 事务隔离级别将会使用驱动或数据源的默认设置;
    • 预处理语句不会被复用,也不会批量处理更新。

    (1)autoCommit传递true即可开启自动提交功能;

    (2)如要使用自己的Connection实例,传递一个Connection实例给connection参数即可;

    (3)mybatis为事务隔离级别调用使用了一个Java枚举包装器,称为TransacationIsolationLevel,若不使用它,将使用JDBC所支持5个隔离级(NONEREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READ 和 SERIALIZABLE) 

    (4)ExecutorType.SIMPLE:这个执行器类型不做特殊的事情,它为每个语句的执行创建一个姓的预处理语句;ExecutorType.REUSE:这个执行器类型会复用处理语句;ExecutorType.BATCH:这个执行器会批量执行所有更新语句,如果select在它们中间执行,必要时请把它们区别开来以保证行为的易读性。

    4. SqlSession

    正如上面所提到的,SqlSession实例在MyBatis中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。

    在SqlSession类中有超过 20 个方法,所以将它们组合成易于理解的分组。

    4.1 执行语句方法

    这些方法被用来执行定义在SQL映射的XML文件中的SELECT、INSERT、UPDATE 和 DELETE 语句。它们都会自行解释,每一句都使用语句的ID属性和参数对象,参数可以是原生类型(自动装箱或包装类)、JavaBean、POJO或Map。

    <T> T selectOne(String statement, Object parameter)
    <E> List<E> selectList(String statement, Object parameter)
    <K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
    int insert(String statement, Object parameter)
    int update(String statement, Object parameter)
    int delete(String statement, Object parameter)  

    selectOne和 selectList的不同仅仅是selectOne必须返回一个对象或null值。如果返回值多于一个,那么就会抛出异常。如果你不知道返回对象的数量,请使用selectList。如果需要查看返回对象是否存在,可行的方案是返回一个值即可(0 或 1)。selectMap稍微特殊一点,因为它会将返回的对象的其中一个属性作为key值,将对象作为 value值,从而将多结果集转为Map类型值。因为并不是所有语句都需要参数,所以这些方法都重载成不需要参数的形式。  

    <T> T selectOne(String statement)
    <E> List<E> selectList(String statement)
    <K,V> Map<K,V> selectMap(String statement, String mapKey)
    int insert(String statement)
    int update(String statement)
    int delete(String statement)

    实例:

    // UserMapper.xml
     <select id="selectUserById" resultMap="UserMap">
      select * from user where
      id = #{id}
     </select>
    
    // UserDao.java
    User u = session1.selectOne("selectUserById", 1);
    

    最后,还有select方法的三个高级版本,它们允许你限制返回行数的范围,或者提供自定义结果控制逻辑,这通常在数据集合庞大的情形下使用(分页查询)。  

    <E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
    <K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
    void select (String statement, Object parameter, ResultHandler<T> handler)
    void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
    

    RowBounds参数会告诉MyBatis略过指定数量的记录,还有限制返回结果的数量。RowBounds类有一个构造方法来接收offset和limit,另外,它们是不可二次赋值的。  

    int offset = 100;
    int limit = 25;
    RowBounds rowBounds = new RowBounds(offset, limit);  

    实例:

    UserMapper.xml
     <select id="selectUsers" resultType= "org.mybatis.mapper.User">
      select * from user
     </select>
    
    UserDao.java
    List<User> us = session1.selectList("selectUsers",null, new RowBounds(2, 1));
    

    ResultHandler参数允许你按你喜欢的方式处理每一行。你可以将它添加到List中、创建Map和Set,或者丢弃每个返回值都可以,它取代了仅保留执行语句过后的总结果列表的死板结果。你可以使用ResultHandler做很多事,并且这是MyBatis自身内部会使用的方法,以创建结果集列表。  

    4.2 批量立即更新方法

    有一个方法可以刷新(执行)存储在JDBC驱动类中的批量更新语句。当你将ExecutorType.BATCH作为ExecutorType使用时可以采用此方法:

    List<BatchResult> flushStatements()
    

    4.3 事务控制

    控制事务作用域有四个方法。当然,如果你已经设置了自动提交或你正在使用外部事务管理器,这就没有任何效果了。然而,如果你正在使用 JDBC 事务管理器,由Connection 实例来控制,那么这四个方法就会派上用场:    

    void commit()
    void commit(boolean force)
    void rollback()
    void rollback(boolean force)
    

    默认情况下 MyBatis 不会自动提交事务,除非它侦测到有插入、更新或删除操作改变了数据库。如果你已经做出了一些改变而没有使用这些方法,那么你可以传递 true 值到 commit 和 rollback 方法来保证事务被正常处理(注意,在自动提交模式或者使用了外部事务管理器的情况下设置 force 值对 session 无效)。很多时候你不用调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。然而,如果你需要在支持多提交和回滚的 session 中获得更多细粒度控制,你可以使用回滚操作来达到目的。  

    4.4 本地缓存

    Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。

    每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询语句本身都会被保存在本地缓存中,那么,相同的查询语句和相同的参数所产生的更改就不会二度影响数据库了。本地缓存会被增删改、提交事务、关闭事务以及关闭 session 所清空。

    默认情况下,本地缓存数据可在整个 session 的周期内使用,这一缓存需要被用来解决循环引用错误和加快重复嵌套查询的速度,所以它可以不被禁用掉,但是你可以设置 localCacheScope=STATEMENT 表示缓存仅在语句执行时有效。

    注意,如果 localCacheScope 被设置为 SESSION,那么 MyBatis 所返回的引用将传递给保存在本地缓存里的相同对象。对返回的对象(例如 list)做出任何更新将会影响本地缓存的内容,进而影响存活在 session 生命周期中的缓存所返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。

    你可以随时调用以下方法来清空本地缓存:

    void clearCache()
    

    4.5 关闭SqlSession  

    void close()
    

    你必须保证的最重要的事情是你要关闭所打开的任何 session。保证做到这点的最佳方式是下面的工作模式:

    SqlSession session = sqlSessionFactory.openSession();
    try {
        // following 3 lines pseudocod for "doing some work"
        session.insert(...);
        session.update(...);
        session.delete(...);
        session.commit();
    } finally {
        session.close();
    }
    

    还有,如果你正在使用jdk 1.7以上的版本还有MyBatis 3.2以上的版本,你可以使用try-with-resources语句:

    try (SqlSession session = sqlSessionFactory.openSession()) {
        // following 3 lines pseudocode for "doing some work"
        session.insert(...);
        session.update(...);
        session.delete(...);
        session.commit();
    }
    

    4.6 获取Configuration

    Configuration getConfiguration()
    

    4.7 获取映射器

    <T> T getMapper(Class<T> type)
    

    上述的各个 insert、update、delete 和 select 方法都很强大,但也有些繁琐,可能会产生类型安全问题并且对于你的 IDE 和单元测试也没有实质性的帮助。  

    因此,一个更通用的方式来执行映射语句是使用映射器类。一个映射器类就是一个仅需声明与 SqlSession 方法相匹配的方法的接口类。  

    实例:

    public interface UserMapper {
     
        User selectUsers();
        User selectUserByName(String name);
        int insertUser(User u);
        
        int insertManyUser(List<User> users);
        
        User selectUserAndRole(int id);
        
        Role selectRole(int id);
        Role selectRoleAndUsers(int id);
        List<User> queryUser();
    }
    

    映射器接口不需要去实现任何接口或继承自任何类。只要方法可以被唯一标识对应的映射语句就可以了。  

    你可以传递多个参数给一个映射器方法。如果你这样做了,默认情况下它们将会以 "param" 字符串紧跟着它们在参数列表中的位置来命名,比如:#{param1}、#{param2}等。如果你想改变参数的名称(只在多参数情况下),那么你可以在参数上使用 @Param("paramName") 注解。

    你也可以给方法传递一个 RowBounds 实例来限制查询结果。

    4.8 映射器注解

    因为最初设计时,MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。而到了 MyBatis 3,就有新选择了。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。这个配置 API 是基于 XML 的 MyBatis 配置的基础,也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销。  

    注解使用对象相对应的 XML描述
    @CacheNamespace <cache> 为给定的命名空间(比如类)配置缓存。属性有:implemetationevictionflushIntervalsizereadWriteblocking 和properties
    @Property N/A <property> 指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:namevalue。(仅在MyBatis 3.4.2以上版本生效)
    @CacheNamespaceRef <cacheRef> 参照另外一个命名空间的缓存来使用。属性有:valuename。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。
    @ConstructorArgs 方法 <constructor> 收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。
    @Arg N/A

    <arg>

    <idArg>

    单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:idcolumnjavaTypejdbcTypetypeHandlerselect 和 resultMap。id 属性是布尔值,来标识用于比较的属性,和<idArg> XML 元素相似。
    @TypeDiscriminator 方法 <discriminator> 一组实例值被用来决定结果映射的表现。属性有:columnjavaTypejdbcTypetypeHandler 和 cases。cases 属性是实例数组。
    @Case N/A <case> 单独实例的值和它对应的映射。属性有:valuetyperesults。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
    @Results 方法 <resultMap> 结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:valueid。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。
    @Result N/A

    <result>

    <id>

    在列和属性或字段之间的单独结果映射。属性有:idcolumnjavaTypejdbcTypetypeHandleronemany。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的<id>相似)的属性。one 属性是单独的联系,和 <association> 相似,而 many 属性是对集合而言的,和<collection>相似。它们这样命名是为了避免名称冲突。
    @One N/A <association> 复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled注意联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。
    @Many N/A <collection> 映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用
    @MapKey 方法   这是一个用在返回值为 Map 的方法上的注解。它能够将存放对象的 List 转化为 key 值为对象的某一属性的 Map。属性有: value,填入的是对象的属性名,作为 Map 的 key 值。
    @Options 方法 映射语句的属性 这个注解提供访问大范围的交换和配置选项的入口,它们通常在映射语句上作为属性出现。Options 注解提供了通俗易懂的方式来访问它们,而不是让每条语句注解变复杂。属性有:useCache=trueflushCache=FlushCachePolicy.DEFAULTresultSetType=FORWARD_ONLYstatementType=PREPAREDfetchSize=-1timeout=-1useGeneratedKeys=falsekeyProperty="id"keyColumn=""resultSets=""。值得一提的是, Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的预期以外的行为。

           注意: keyColumn 属性只在某些数据库中有效(如 Oracle、PostgreSQL等)。请在插入语句一节查看更多关于 keyColumn 和 keyProperty 两者的有效值详情。

    @Insert

    @Update

    @Delete

    @Select

    方法

    <insert>

    <update>

    <delete>

    <select>

    这四个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串之间先会被填充一个空格再连接成单个完整的字符串。这有效避免了以 Java 代码构建 SQL 语句时的“丢失空格”的问题。然而,你也可以提前手动连接好字符串。属性有:value,填入的值是用来组成单个 SQL 语句的字符串数组。

    @InsertProvider

    @UpdateProvider

    @DeleteProvider

    @SelectProvider

    方法

    <insert>

    <update>

    <delete>

    <select>

    允许构建动态 SQL。这些备选的 SQL 注解允许你指定类名和返回在运行时执行的 SQL 语句的方法。(自从MyBatis 3.4.6开始,你可以用 CharSequence 代替 String 来返回类型返回值了。)当执行映射语句的时候,MyBatis 会实例化类并执行方法,类和方法就是填入了注解的值。你可以把已经传递给映射方法了的对象作为参数,"Mapper interface type" 和 "Mapper method" 会经过 ProviderContext (仅在MyBatis 3.4.5及以上支持)作为参数值。(MyBatis 3.4及以上的版本,支持多参数传入)属性有: typemethodtype 属性需填入类。method 需填入该类定义了的方法名。注意 接下来的小节将会讨论类,能帮助你更轻松地构建动态 SQL。
    @Param 参数 N/A 如果你的映射方法的形参有多个,这个注解使用在映射方法的参数上就能为它们取自定义名字。若不给出自定义名字,多参数(不包括 RowBounds 参数)则先以 "param" 作前缀,再加上它们的参数位置作为参数别名。例如 #{param1}#{param2},这个是默认值。如果注解是 @Param("person"),那么参数就会被命名为 #{person}
    @SelectKey 方法 <selectKey> 这个注解的功能与 <selectKey> 标签完全一致,用在已经被 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 注解了的方法上。若在未被上述四个注解的方法上作 @SelectKey 注解则视为无效。如果你指定了 @SelectKey 注解,那么 MyBatis 就会忽略掉由 @Options 注解所设置的生成主键或设置(configuration)属性。属性有:statement 填入将会被执行的 SQL 字符串数组,keyProperty 填入将会被更新的参数对象的属性的值,before 填入 true 或 false 以指明 SQL 语句应被在插入语句的之前还是之后执行。resultType 填入 keyProperty 的 Java 类型和用 Statement、 PreparedStatement 和 CallableStatement 中的 STATEMENT、 PREPARED 或 CALLABLE 中任一值填入 statementType。默认值是 PREPARED
    @ResultMap 方法 N/A 这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 <resultMap> 的id。这使得注解的 select 可以复用那些定义在 XML 中的 ResultMap。如果同一 select 注解中还存在 @Results 或者 @ConstructorArgs,那么这两个注解将被此注解覆盖。
    @ResultType 方法 N/A 此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap注解。如果结果类型在 XML 的 <select> 节点中指定了,就不需要其他的注解了。其他情况下则使用此注解。比如,如果 @Select 注解在一个将使用结果处理器的方法上,那么返回类型必须是 void 并且这个注解(或者@ResultMap)必选。这个注解仅在方法返回类型是 void 的情况下生效。
    @Flush 方法 N/A 如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上)

    实例:

        @Select("select * from user where id=#{id}")
        User selectOne(int id);
    

    5. SQL语句构建器

    Java程序员面对的最痛苦的事情之一就是在Java代码中嵌入SQL语句。MyBatis在它的XML映射特性中有一个强大的动态SQL生成方案。但有时在Java代码内部创建SQL语句也是必要的。此时,MyBatis有另外一个特性可以帮到你,在减少典型的加号,引号,新行,格式化问题和嵌入条件来处理多余的逗号或 AND 连接词之前。

    例如:

    String sql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, "
    "P.LAST_NAME,P.CREATED_ON, P.UPDATED_ON " +
    "FROM PERSON P, ACCOUNT A " +
    "INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID " +
    "INNER JOIN COMPANY C on D.COMPANY_ID = C.ID " +
    "WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) " +
    "OR (P.LAST_NAME like ?) " +
    "GROUP BY P.ID " +
    "HAVING (P.LAST_NAME like ?) " +
    "OR (P.FIRST_NAME like ?) " +
    "ORDER BY P.ID, P.FULL_NAME";
    

    使用SQL类,简单地创建一个实例来调用方法生成SQL语句。上面示例中的问题就像重写SQL类那样:

    private String selectPersonSql() {
      return new SQL() {{
        SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
        SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
        FROM("PERSON P");
        FROM("ACCOUNT A");
        INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
        INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
        WHERE("P.ID = A.ID");
        WHERE("P.FIRST_NAME like ?");
        OR();
        WHERE("P.LAST_NAME like ?");
        GROUP_BY("P.ID");
        HAVING("P.LAST_NAME like ?");
        OR();
        HAVING("P.FIRST_NAME like ?");
        ORDER_BY("P.ID");
        ORDER_BY("P.FULL_NAME");
      }}.toString();
    }
    

    6. 日志

    Mybatis 的内置日志工厂提供日志功能,内置日志工厂将日志交给以下其中一种工具作代理:

    • SLF4J
    • Apache Commons Logging
    • Log4j 2
    • Log4j
    • JDK logging

    MyBatis 内置日志工厂基于运行时自省机制选择合适的日志工具。它会使用第一个查找得到的工具(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择别的日志工具。

    <configuration>
      <settings>
        ...
        <setting name="logImpl" value="LOG4J"/>
        ...
      </settings>
    </configuration>
    

    logImpl 可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了接口 org.apache.ibatis.logging.Log 的,且构造方法是以字符串为参数的类的完全限定名。  

    x. 参考资料

    http://www.mybatis.org/mybatis-3/zh/java-api.html

    http://www.mybatis.org/mybatis-3/zh/statement-builders.html

    http://www.mybatis.org/mybatis-3/zh/logging.html

  • 相关阅读:
    LTE第一章 介绍
    一本关于 LTE 非常好的书籍
    Memcached安装卸载
    很好很实用的.net、网站系统后台模板
    MS SQL 当记录不存在时插入insert INTO not exists
    数据库存储过程缺点总结
    存储过程是罪恶
    树形数据查询示例
    安装Discuz!论坛时提示“mysqli_connect() 不支持 advice_mysqli_connect”
    sql server中将一个表中的部分数据插入到另一个表中
  • 原文地址:https://www.cnblogs.com/lujiango/p/8665543.html
Copyright © 2011-2022 走看看