zoukankan      html  css  js  c++  java
  • MyBatis-日志、分页、一对多or多对一

    日志

    日志工厂

    如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!

    曾经:sout、debug

    现在:日志工厂

    • SLF4J
    • LOG4J【掌握】
    • LOG4J2
    • JDK_LOGGING
    • COMMONS_LOGGING
    • STDOUT_LOGGING【掌握】
    • NO_LOGGING

    在Mybatis中具体使用那个日志实现,在设置中设定!

    STDOUT_LOGGING标准日志输出

    在mybatis核心配置文件中,配置我们的日志!

    <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    

    Log4j

    什么事log4j

    • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
    • 我们也可以控制每一条日志的输出格式
    • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
    • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

    1.先导入log4j的包

        <dependencies>
            <!-- https://mvnrepository.com/artifact/log4j/log4j -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
        </dependencies>
    
    

    2.log4j.properties

    log4j.rootLogger=debug, stdout, R
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    # Pattern to output the caller's file name and line number.
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
    log4j.appender.R=org.apache.log4j.RollingFileAppender
    log4j.appender.R.File=example.log
    log4j.appender.R.MaxFileSize=100KB
    # Keep one backup file
    log4j.appender.R.MaxBackupIndex=5
    log4j.appender.R.layout=org.apache.log4j.PatternLayout
    log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
    
    
    
    
    

    3.配置log4j为日志实现

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

    4.log4j的使用!直接测试运行刚才的查询

    DEBUG [main] (LogFactory.java:105) - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    DEBUG [main] (LogFactory.java:105) - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
    DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
    DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
    DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
    DEBUG [main] (PooledDataSource.java:353) - PooledDataSource forcefully closed/removed all connections.
    DEBUG [main] (JdbcTransaction.java:136) - Opening JDBC Connection
    Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
    DEBUG [main] (PooledDataSource.java:424) - Created connection 2049051802.
    DEBUG [main] (JdbcTransaction.java:100) - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
    DEBUG [main] (BaseJdbcLogger.java:143) - ==>  Preparing: /*定义sql*/ select * from mybatis.user where id = ?; 
    DEBUG [main] (BaseJdbcLogger.java:143) - ==> Parameters: 1(Integer)
    DEBUG [main] (BaseJdbcLogger.java:143) - <==      Total: 1
    User{id=1, name='狂神', password='123456'}
    DEBUG [main] (JdbcTransaction.java:122) - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
    DEBUG [main] (JdbcTransaction.java:90) - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7a220c9a]
    DEBUG [main] (PooledDataSource.java:381) - Returned connection 2049051802 to pool.
    
    
    Disconnected from the target VM, address: '127.0.0.1:58296', transport: 'socket'
    
    Process finished with exit code 0
    
    

    简单使用

    1. 在要使用Log4j 的类中,导入org.apache.log4j.Logger;

    2. 日志对象,加载参数为当前类的class

       static Logger logger = Logger.getLogger(UserDaoTest.class);
      
    3. 日志级别

      logger.info("info:进入了testLog4j方法");
      logger.debug("debug:进入了testLog4j");
      logger.error("error:进入了testLog4j");
      

    分页

    思考:为什么要分页?

    • 减少数据的处理量

    使用Limit分页

    select * from user limit startIndex,pageSize
    

    使用Mybatis实现分页,核心SQL

    1. 接口

      //分页
      List<User> getUserByLimit(Map<String,Integer> map);
      
    2. Mapper.xml

      <!--分页-->
      <select id="getUserByLimit" parameterType="map" resultMap="UserMap">
          select * from mybatis.user limit #{startIndex},#{pageSize}
      </select>
      
    3. 测试

      @Test
          public void getUserByLimit(){
              SqlSession sqlSession = MyBatisUtils.getSqlSession();
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              HashMap<String, Integer> map = new HashMap<>();
              map.put("startIndex",0);
              map.put("pageSize",2);
      
      
              List<User> userList = mapper.getUserByLimit(map);
              for (User user : userList) {
                  System.out.println(user);
              }
              sqlSession.close();
          }
      

    RowBounds分页

    不再使用SQL实现分页

    1. 接口

      List<User> getUserByRowBounds();
      
    2. mapper.xml

      <!--分页2-->
      <select id="getUserByRowBounds" resultMap="UserMap">
          select * from mybatis.user
      </select>
      
    3. 测试

      @Test
      public void getUserByRowBounds(){
          SqlSession sqlSession = MyBatisUtils.getSqlSession();
          //RowBounds实现
          RowBounds rowBounds = new RowBounds(1, 2);
      
          //通过java代码层面实现分页
          List<User> userList = sqlSession.selectList("com.rui.dao.UserMapper.getUserByRowBounds",null,rowBounds);
      
          for (User user : userList) {
              System.out.println(user);
          }
          sqlSession.close();
      }
      

    分页插件

    多对一处理

    多对一:

    • 多个学生,对应一个老师
    • 对于学生这边而言,关联...多个学生,关联一个老师【多对一】
    • 对于老师而言,集合,一个老师又很多学生【一对多】

    SQL:

    CREATE TABLE `teacher`(
    `id` int(10) Not null,
    `name` VARCHAR(30) DEFAULT NULL,
    PRIMARY KEY (`id`)
    )ENGINE=INNODB DEFAULT CHARSET=utf8
    
    INSERT INTO teacher(`id`,`name`) VALUES (1,'秦老师');
    
    CREATE TABLE `student`(
    `id` int(10) Not null,
    `name` VARCHAR(30) DEFAULT NULL,
    `tid` INT(10) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `fktid`(`tid`),
    CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
    )ENGINE=INNODB DEFAULT CHARSET=utf8
    
    INSERT INTO student(`id`,`name`,`tid`) VALUES (1,'小明',1);
    INSERT INTO student(`id`,`name`,`tid`) VALUES (2,'小红',1);
    INSERT INTO student(`id`,`name`,`tid`) VALUES (3,'小张',1);
    INSERT INTO student(`id`,`name`,`tid`) VALUES (4,'小李',1);
    INSERT INTO student(`id`,`name`,`tid`) VALUES (5,'小王',1);
    

    测试环境

    1. 导入lombok
    2. 新建实体类Teacher,Student
    3. 新建Mapper接口
    4. 建立Mapper.XML文件
    5. 在核心配置文件中绑定注册我们的MApper接口或者文件!【方式很多,随意选】
    6. 测试查询是否成功!

    按照查询嵌套处理

    <!--
    思路:
        1、查询所有的学生信息
        2、根据查询出来的学生的id的tid,寻找对应的老师! -子查询
    
    -->
    
    <select id="getStudent" resultMap="StudentTeacher">
    select * from student
    </select>
    
    <resultMap id="StudentTeacher" type="com.rui.pojo.Student">
        <!--复杂的属性,我们需要单独处理  对象:association  集合:collection-->
        <association property="teacher" column="tid" javaType="com.rui.pojo.Teacher" select="getTeacher"/>
    </resultMap>
    
    <select id="getTeacher" resultType="com.rui.pojo.Teacher">
        select * from teacher where id = #{id}
    </select>
    

    按照结果嵌套处理

    <!--按照结果嵌套处理-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from student s,teacher t
        where s.tid=t.id;
    </select>
    
    <resultMap id="StudentTeacher2" type="com.rui.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="com.rui.pojo.Teacher">
            <result property="id" column="tid"></result>
            <result property="name" column="tname"></result>
        </association>
    
    </resultMap>
    

    回顾Mysql多对一查询方式:

    • 子查询
    • 联表查询

    一对多处理

    比如:一个老师拥有多个学生!

    对于老师而言,就是一对多的关系!

    环境搭建

    1. 环境搭建,和刚才一样

    实体类

    @Data
    public class Teacher {
        private int id;
        private String name;
    
        //一个老师拥有多个学生
        private List<Student> students;
    }
    
    @Data
    public class Student {
        private int id;
        private String name;
        private int tid;
    
    }
    

    按照结果嵌套处理

    <!--按结果嵌套查询-->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from student s,teacher t
        where s.tid=t.id and t.id = #{tid}
    </select>
    <resultMap id="TeacherStudent" type="com.rui.pojo.Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!--复杂的属性,我们需要单独处理  对象:association  集合:collection
            javaType="" 指定属性的类型
            集合中的泛型信息,我们使用ofType获取
        -->
        <collection property="students" ofType="com.rui.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>
    

    按照查询嵌套处理

    <select id="getTeacher2" resultMap="TeacherStudent2">
        select * from mybatis.teacher where id = #{tid}
    </select>
    <resultMap id="TeacherStudent2" type="com.rui.pojo.Teacher">
        <collection property="students" javaType="ArrayList" ofType="com.rui.pojo.Student" select="getStudentByTeacherId" column="id"/>
    </resultMap>
    
    <select id="getStudentByTeacherId" resultType="com.rui.pojo.Student">
        select * from mybatis.student where tid = #{tid}
    </select>
    

    小节

    1. 关联-association【多对一】
    2. 集合-collection 【一对多】
    3. javaType & ofType
      1. JavaType用来指定实体类中属性的类型
      2. ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!

    注意点:

    • 保证SQL的可读性,尽量保证通俗易懂
    • 注意一对多和多对一中,属性名和字段的问题!
    • 如果问题不好排查错误,可以使用日志,建议使用Log4j

    慢SQL 1S 1000S

    面试高频

    • Mysql引擎
    • InnoDB底层原理
    • 索引
    • 索引优化!

    动态SQL

    什么事动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句

    利用动态SQL这一特性可以彻底摆脱这种痛苦

    动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

    • if
    • choose (when, otherwise)
    • trim (where, set)
    • foreach

    搭建环境

    CREATE TABLE `bolg`(
    	`id` VARCHAR(50) NOT NULL COMMENT '博客id',
    	`title` VARCHAR(100) not null comment '博客标题',
    	`author` VARCHAR(30) not null comment '博客作者',
    	`creat_time` datetime not null comment '创建时间',
    	`views` int(30) not null comment '浏览量'
    )ENGINE=InnoDB DEFAULT CHARSET=utf8
    

    创建一个基础工程

    1. 导包

    2. 编写配置文件

    3. 编写实体类

      @Data
      public class Blog {
          private int id;
          private String title;
          private String author;
          private Date creatTime;
          private int views;
      }
      
    4. 编写实体类对应的Mapper接口和Mapper.xml

    IF

    <select id="queryBlogIF" parameterType="map" resultType="com.rui.pojo.Blog">
        select * from mybatis.bolg where 1=1
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>
    
    @Test
    public void queryBlogIF(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        map.put("author","尹锐");
        List<Blog> blogs = mapper.queryBlogIF(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }
    

    choose (when, otherwise)

    <select id="queryBlogChoose" parameterType="map" resultType="com.rui.pojo.Blog">
        select * from mybatis.bolg
        <where>
            <choose>
                <when test="title != null">
                    title=#{title}
                </when>
                <when test="author!=null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = #{views}
                </otherwise>
            </choose>
        </where>
    </select>
    

    trim, (where, set)

    select * from mybatis.bolg
    <where>
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    </where>
    
    <update id="updateBlog" parameterType="map">
        update mybatis.bolg
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author},
            </if>
        </set>
        where id = #{id}
    </update>
    

    所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一些逻辑代码

    if

    Where,set,choose,when

    SQL片段

    有的时候,我们可能会将一些公共的部分抽取出来,方便复用!

    1. 使用SQL标签抽取公共的部分

      <sql id="if-title-author">
          <if test="title != null">
              title = #{title}
          </if>
          <if test="author != null">
              and author = #{author}
          </if>
      </sql>
      
    2. 在需要使用的地方使用Include标签引用即可

      <select id="queryBlogIF" parameterType="map" resultType="com.rui.pojo.Blog">
          select * from mybatis.bolg
          <where>
              <include refid="if-title-author"></include>
          </where>
      </select>
      

      注意事项:

      • 最好基于单表来定义SQL片段!
      • 不要存在where或者set标签,片段里尽量只有if就好了

      Foreach

      select * from user where 1=1 and 
        <foreach item="id" index="index" collection="ids"
            open="(" separator="or" close=")">
              #{id}
        </foreach>
      
      (id=1 or id=2 or id=3)
      
      <!--
      select * from mybatis.bolg where 1=1 and (id=1 or id=2 or id=3)
      
      我们现在传递一个万能的map,这个map中可以存在一个map
      -->
      <select id="queryBlogForeach" parameterType="map" resultType="com.rui.pojo.Blog">
          select * from mybatis.bolg
      
          <where>
          <foreach collection="ids" item="id" open="(" close=")" separator="or">
              id = #{id}
          </foreach>
          </where>
      </select>
      

      动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了

      建议:

      • ​ 先在Mysql中写出完整的SQL,在对应的去修改称为我们的动态SQL

    缓存

    简介

    查询	:		连接数据库,耗资源!
    		一次查询的结果,给他暂存在一个可以直接取到的地方!--->内存	:	缓存
    		
    我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
    
    1. 什么事缓存[Cache]?

      • 存在内存中的临时数据。

      • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,

        从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

    2. 为什么使用缓存?

      • 减少和数据库的交互次数,减少系统开销,提高系统效率。
    3. 什么样的数据能使用缓存?

      • 经常查询并且不经常改变的数据。

    Mybatis缓存

    • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
    • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
      • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
      • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
      • 为了提扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

    一级缓存

    • 一级缓存也叫本地缓存:SqlSession
      • 与数据库同义词会话期间查询到的数据会放在本地缓存中。
      • 以后如果需要获取相同的数据,直接从缓存中拿,没有必要再去查询数据;

    测试步骤:

    1. 开启日志!
    2. 测试在一个Session中查询两次相同的记录
    3. 查看日志输出

    缓存失效的情况:

    1. 查询不同的东西

    2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

    3. 查询不同的Mapper.xml

    4. 手动清理缓存!

      sqlsession.clearCache(); //手动清理缓存
      

    小节:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

    一级缓存就是一个Map。

    二级缓存

    • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
    • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
    • 工作机制
      • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
      • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据会被保存到二级缓存中;
      • 新的会话查询信息,就可以从二级缓存中获取内容;
      • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

    步骤:

    1. 开启全局缓存

      <!--显式的开启全局缓存-->
      <setting name="cacheEnabled" value="true"/>
      
    2. 在要使用二级缓存的Mapper中开启

      <!--在当前Mapper.xml中使用二级缓存-->
      <cache/>
      也可以自定义参数
      <cache eviction="FIFO"
             flushInterval="60000"
             size="512"
             readOnly="true"/>
      
    3. 测试

      1. 问题:我们需要将实体类序列化!否则就会报错

         java.io.NotSerializableException: com.rui.pojo.User
        

    小结:

    • 只要开启了二级缓存,在同一个Mapper下就有效
    • 所有的数据都会先放在一级缓存中;
    • 只有当会话提交,或者关闭的时候,才会提交到二级缓存中!

    缓存原理

    自定义缓存-encache

    EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
    

    要在程序中使用ehcache,先要导包!

    <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
    
    

    然后在mapper中指定使用ehcache缓存实现

    <!--在当前Mapper.xml中使用二级缓存-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    

    导入配置文件 ehcache.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
             updateCheck="false">
        <!--
           diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
           user.home – 用户主目录
           user.dir  – 用户当前工作目录
           java.io.tmpdir – 默认临时文件路径
         -->
        <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
        <!--
           defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
         -->
        <!--
          name:缓存名称。
          maxElementsInMemory:缓存最大数目
          maxElementsOnDisk:硬盘最大缓存个数。
          eternal:对象是否永久有效,一但设置了,timeout将不起作用。
          overflowToDisk:是否保存到磁盘,当系统当机时
          timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
          timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
          diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
          diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
          diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
          memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
          clearOnFlush:内存数量最大时是否清除。
          memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
          FIFO,first in first out,这个是大家最熟的,先进先出。
          LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
          LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
       -->
        <defaultCache
                eternal="false"
                maxElementsInMemory="10000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="259200"
                memoryStoreEvictionPolicy="LRU"/>
     
        <cache
                name="cloud_user"
                eternal="false"
                maxElementsInMemory="5000"
                overflowToDisk="false"
                diskPersistent="false"
                timeToIdleSeconds="1800"
                timeToLiveSeconds="1800"
                memoryStoreEvictionPolicy="LRU"/>
     
    </ehcache>
    
  • 相关阅读:
    leetcode562- Longest Line of Consecutive One in Matrix- medium
    leetcode118- Pascal's Triangle I & II- easy
    leetcode524- Longest Word in Dictionary through Deleting- medium
    leetcode128- Longest Consecutive Sequence- hard
    leetcode22- Generate Parentheses- medium
    leetcode17- Letter Combinations of a Phone Number- medium
    leetcode678- Valid Parenthesis String- medium
    php截取字符串的实例代码(支持utf-8)
    php中封装的curl函数(抓取数据)
    linux计划任务运行php文件的方法分享
  • 原文地址:https://www.cnblogs.com/pengcode/p/12838996.html
Copyright © 2011-2022 走看看