动力节点Mybatis (13-17)

⼗三、MyBatis的⾼级映射及延迟加载

模块名:mybatis- 010 -advanced-mapping
打包⽅式:jar
依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
配置⽂件:mybatis-config.xml、logback.xml、jdbc.properties
拷⻉⼯具类:SqlSessionUtil
准备数据库表:⼀个班级对应多个学⽣。班级表:t_clazz。学⽣表:t_stu

 

创建好pojo:Student、Clazz  

public class Clazz {
    private Integer cid;
    private String cname;

    public Clazz(){}

    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }

    //后面跟get,set,toString方法
//学生信息
public class Student {
    private Integer sid;
    private String sname;

    public Student(){}

    public Student(Integer sid, String sname) {
        this.sid = sid;
        this.sname = sname;
    }

    //后面跟get,set,toString方法

创建mapper接⼝:StudentMapper、ClazzMapper
创建mapper映射⽂件:StudentMapper.xml、ClazzMapper.xml

13.1 多对⼀

多种⽅式,常⻅的包括三种:
第⼀种⽅式:⼀条SQL语句,级联属性映射。
第⼆种⽅式:⼀条SQL语句,association。
第三种⽅式:两条SQL语句,分步查询。(这种⽅式常⽤:优点⼀是可复⽤。优点⼆是⽀持懒加
载。)
第⼀种⽅式:级联属性映射
pojo类Student中添加⼀个属性:Clazz clazz; 表示学⽣关联的班级对象。
public class Clazz {
    private Integer cid;
    private String cname;
    private List<Student> stus;

    public Clazz(){}

    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }

    //后面跟get,set,toString方法
StudentMapper接口:
    //根据id获取学生信息,同时获取学生关联的班级信息,返回学生对象,但是学生对象中含有班级对象
    Student selectById(Integer id);
StudentMapper.xml:
    <!--  多对一映射的第一种方式:一条SQL语句,联机属性映射  -->
    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"/>
        <result property="clazz.cid" column="cid"/>
        <result property="clazz.cname" column="cname"/>
    </resultMap>
    <select id="selectById" resultMap="studentResultMap">
        select s.sid,
               s.sname,
               c.cid,
               c.cname
        from t_stu s
                 left join t_clazz c on s.cid = c.cid
        where s.sid = #{sid}
    </select>
StudentMapperTest:
    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectById(1);
        System.out.println(student.getSid());   //1
        System.out.println(student.getSname()); //张三
        System.out.println(student.getClazz().getCid());    //1000
        System.out.println(student.getClazz().getCname());  //大数据201
        System.out.println(student);
        sqlSession.close();
    }

第⼆种⽅式:association
其他位置都不需要修改,只需要修改resultMap中的配置:association即可。
StudentMapper接口:
    //一条SQL,association
    Student selectByIdAssociation(Integer id);
StudentMapper.xml:
    <!--    ⼀条SQL语句,association。-->
    <resultMap id="studentResultMapAssociation" type="student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <!-- association翻译为:关联。一个student对象关联⼀个clazz对象。
             property:提供要映射的POJO类的属性名
             javaType:用来指定要映射的java类型
        -->
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        </association>
    </resultMap>
    <select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
        select s.sid,
               s.sname,
               c.cid,
               c.cname
        from t_stu s
                 left join t_clazz c on s.cid = c.cid
        where s.sid = #{sid}
    </select>
StudentMapperTest:
    @Test
    public void testSelectByIdAssociation(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student=mapper.selectByIdAssociation(2);
        System.out.println(student);
        sqlSession.close();
    }

第三种⽅式:分步查询
其他位置不需要修改,只需要修改以及添加以下三处:
第⼀处:association中select位置填写sqlId。sqlId=namespace+id。其中column属性作为这条⼦sql语句的条件。
第⼆处:在ClazzMapper接⼝中添加⽅法
第三处:在ClazzMapper.xml⽂件中进⾏配置
StudentMapper接口:
    //分步查询第一步:先根据学生的sid查询学生的信息
    Student selectByIdStep1(Integer id);
StudentMapper.xml:
    <!-- 两天SQL语句,完成多对一的分步查询   -->
    <!-- 这里是第一步:根据学生的id查询学生的所有信息,这些信息当中含有班级id(cid)   -->
    <!--
        分步查询的优点:
            第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用。)
            第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。
        什么是延迟加载(懒加载),有什么用?
            延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。
            作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
        在mybatis当中怎么开启延迟加载呢?
            association标签中添加fetchType="lazy"
            注意:默认情况下是没有开启延迟加载的。需要设置:fetchType="lazy"
            这种在association标签中配置fetchType="lazy",是局部的设置,只对当前的association关联的sql语句起作用。

        在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
            在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true

        实际开发中的模式:
            把全局的延迟加载打开。
            如果某一步不需要使用延迟加载,请设置:fetchType="eager"
    -->
    <resultMap id="studentResultMapByStep" type="student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz"
                    select="com.powernode.mybatis.mapper.ClazzMapper.selectById"
                     column="cid"/>
    </resultMap>
    <select id="selectByIdStep1" resultMap="studentResultMapByStep">
        select sid,sname,cid from t_stu where sid = #{sid}
    </select>

ClazzMapper接口:

    //分步查询第二步:根据cid获取班级信息
    Clazz selectById(Integer cid);

ClazzMapper.xml:

    <!-- 分步查询第二部:根据cid获取班级信息   -->
    <select id="selectById" resultType="Clazz">
        select cid, cname
        from t_clazz
        where cid = #{cid}
    </select>
StudentMapperTest:
    @Test
    public void testSelectByIdStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student =mapper.selectByIdStep1(5);
        //第一步:Preparing: select sid,sname,cid from t_stu where sid = ?
        //第二步:Preparing: select cid, cname from t_clazz where cid = ?
        System.out.println(student);
        sqlSession.close();
    }
分步优点:
   ●第⼀个优点:代码复⽤性增强。
   ●第⼆个优点:⽀持延迟加载。【暂时访问不到的数据可以先不查询。提⾼程序的执⾏效率。】

13.2 多对⼀延迟加载  

要想⽀持延迟加载,⾮常简单,只需要在association标签中添加fetchType="lazy"即可。
修改StudentMapper.xml⽂件:
StudentMapper.xml:
<!--     什么是延迟加载(懒加载),有什么用?
            延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。
            作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。
        在mybatis当中怎么开启延迟加载呢?
            association标签中添加fetchType="lazy"
            注意:默认情况下是没有开启延迟加载的。需要设置:fetchType="lazy"
            这种在association标签中配置fetchType="lazy",是局部的设置,只对当前的association关联的sql语句起作用。

        在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:
            在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true

        实际开发中的模式:
            把全局的延迟加载打开。
            如果某一步不需要使用延迟加载,请设置:fetchType="eager"
    -->
    <resultMap id="studentResultMapByStep" type="student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz"
                    select="com.powernode.mybatis.mapper.ClazzMapper.selectById"
                     column="cid"
                    fetchType="eager"/>
    </resultMap>
    <select id="selectByIdStep1" resultMap="studentResultMapByStep">
        select sid,sname,cid from t_stu where sid = #{sid}
    </select>
StudentMapperTest:
    @Test
    public void testSelectByIdStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student =mapper.selectByIdStep1(5);
        //第一步:Preparing: select sid,sname,cid from t_stu where sid = ?
        //第二步:Preparing: select cid, cname from t_clazz where cid = ?
        //System.out.println(student);

        //看延迟加载,现在只需要看学生的名字
        //Preparing: select sid,sname,cid from t_stu where sid = ?
        //在添加了懒加载的情况下,可以看到只执行了一条sql语句,第二条没用到,就没执行
        System.out.println(student.getSname());
        sqlSession.close();
    }
实际开发中的模式:
    把全局的延迟加载打开。
    如果某一步不需要使用延迟加载,请设置:fetchType="eager"
这样的话,针对某个特定的sql,你就关闭了延迟加载机制。
后期我们要不要开启延迟加载机制,主要看实际的业务需求是怎样的。
    <settings>
        <!--延迟加载的全局开关,默认值false-->
        <!--所有只要带有分步的,都要采用延时加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

13.3 ⼀对多

⼀对多的实现,通常是在⼀的⼀⽅中有List集合属性。
在Clazz类中添加List<Student> stus; 属性。
//学生信息
public class Student {
    private Integer sid;
    private String sname;
    private Clazz clazz;

    public Student(){}

    public Student(Integer sid, String sname) {
        this.sid = sid;
        this.sname = sname;
    }

    //后面跟get,set,toString方法
●第⼀种⽅式:collection
●第⼆种⽅式:分步查询
第⼀种⽅式:collection
ClazzMapper接口:
    //根据班级编号,查询班级信息
    Clazz selectByCollection(Integer cid);
ClazzMapper.xml:
    <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!--  一对多,这里是collection。collection是集合的意思-->
        <!--ofType 用来指定集合当中的元素类型-->
        <collection property="stus" ofType="Student">
            <id property="sid" column="sid"/>
            <result property="sname" column="sname"/>
        </collection>
    </resultMap>
    <select id="selectByCollection" resultMap="clazzResultMap">
        select c.cid, c.cname, s.sid, s.sname
        from t_clazz as c
                 left join t_stu as s on c.cid = s.cid
        where c.cid = #{cid}
    </select>
ClazzMapperTest:
    @Test
    public void testSelectByCollection(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
        Clazz clazz = mapper.selectByCollection(1000);
        System.out.println(clazz);
        sqlSession.close();
    }
第⼆种⽅式:分步查询
ClazzMapper接口:
    //分步查询。第一步:根据班级编号获取班级信息
    Clazz selectByStep1(Integer cid);
ClazzMapper.xml:
    <!--分步查询第一步:根据班级的cid获取班级信息 -->
    <resultMap id="clazzResultMapStep" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection property="stus"
                    select="com.powernode.mybatis.mapper.StudentMapper.selectByCidStep2"
                    column="cid"/>
    </resultMap>
    <select id="selectByStep1" resultMap="clazzResultMapStep">
        select cid, cname
        from t_clazz
        where cid = #{cid}
    </select>
StudentMapper接口:
    //分步查询第二步:根据班级编号查询学生信息
    List<Student> selectByCidStep2(Integer cid);
StudentMapper.xml:
    <select id="selectByCidStep2" resultType="student">
        select * from t_stu where cid=#{cid}
    </select>
ClazzMapperTest:
    @Test
    public void testSelectByStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
        Clazz clazz = mapper.selectByStep1(1000);
        //Preparing: select cid, cname from t_clazz where cid = ?
        //Preparing: select * from t_stu where cid=?
        //System.out.println(clazz);

        //只访问班级名字时,延迟加载才开始使用
        //Preparing: select cid, cname from t_clazz where cid = ?
        System.out.println(clazz.getCname());
        sqlSession.close();
    }

13.4 ⼀对多延迟加载

⼀对多延迟加载机制和多对⼀是⼀样的。同样是通过两种⽅式:
第⼀种:fetchType="lazy"
第⼆种:修改全局的配置setting, lazyLoadingEnabled=true, 如果开启全局延迟加载,想让某个 sql不使⽤延迟加载:fetchType="eager"

⼗四、MyBatis的缓存

缓存:cache
缓存的作⽤:通过减少IO的⽅式,来提⾼程序的执⾏效率。
mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下⼀次还是这条select语句的话,直接从缓存中取,不再查数据库。⼀⽅⾯是减少了IO。另⼀⽅⾯不再执⾏繁琐的查找算法。效率⼤⼤提升。
mybatis缓存包括:
⼀级缓存:将查询到的数据存储到SqlSession中。(范围小,只针对当前的会话)
⼆级缓存:将查询到的数据存储到SqlSessionFactory中。(范围大,针对当前的数据库)
或者集成其它第三⽅的缓存:⽐如EhCache【Java语⾔开发的】、Memcache【C语⾔开发的】
等。
缓存只针对于DQL语句,也就是说缓存机制只对应select语句。

14.1 ⼀级缓存

⼀级缓存默认是开启的。不需要做任何配置。
原理:只要使⽤同⼀个SqlSession对象执⾏同⼀条SQL语句,就会⾛缓存。
模块名:mybatis- 011 -cache
使用一级缓存:同一个sqlSession 和同一个sql语句
    @Test
    public void testSelectById() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);

        Car car1 = mapper.selectById(1L);
        System.out.println(car1);

        Car car2 = mapper.selectById(1L);
        System.out.println(car2);
        //Preparing: select * from t_car where id=?
       //[main] DEBUG com.powernode.mybatis.mapper.CarMapper.selectById - <==      Total: 1
      //Car{id=1, carNum='1001', brand='宝马', guidePrice=10.0, produceTime='2020-10-11', carType='电车'}
      //Car{id=1, carNum='1001', brand='宝马', guidePrice=10.0, produceTime='2020-10-11', carType='电车'}
        //只执行了car1的一次,而car2是直接输出的  走的是一级缓存
        sqlSession.close();
    }

当使用两个不同的sqlSession时:

   @Test
    public void testSelectById() throws Exception {
        //不使用工具类的原因是有ThreadLocal<SqlSession> 新建出来的sqlSession是同一个
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);

        Car car1 = mapper1.selectById(1L);
        Car car2 = mapper2.selectById(1L);

        //Preparing: select * from t_car where id=?
        System.out.println(car1);
        //Preparing: select * from t_car where id=?
        //此时sqlSession不同,就不走缓存了,跟缓存没关系
        System.out.println(car2);
        sqlSession1.close();
        sqlSession2.close();
    }
    @Test
    public void testSelectById() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);

        Car car1 = mapper1.selectById(1L);
        System.out.println(car1);

        //手动清空一级缓存
//        sqlSession.clearCache();

        //在这里执行了insert,update,delete语句,而且和表没关系
        //此时car2不会走缓存
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        mapper.insertClazz(1003,"大数据203");


        CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);
        Car car2 = mapper2.selectById(1L);
        System.out.println(car2);

        sqlSession.commit();
        sqlSession.close();
    }

什么情况下不⾛缓存?
   ●第⼀种:不同的SqlSession对象。
   ●第⼆种:查询条件变化了。
⼀级缓存失效情况包括两种:
   ●第⼀种:第⼀次查询和第⼆次查询之间,⼿动清空了⼀级缓存。
         sqlSession . clearCache ();
   ●第⼆种:第⼀次查询和第⼆次查询之间,执⾏了增删改操作。【这个增删改和哪张表没有关系,只要有insert delete update操作,⼀级缓存就失效。】

14.2 ⼆级缓存

⼆级缓存的范围是SqlSessionFactory。
使⽤⼆级缓存需要具备以下⼏个条件:
1. <setting name="cacheEnabled" value="true"> 全局性地开启或关闭所有映射器配置⽂件中已配置的任何缓存。默认就是true,⽆需设置。
2. 在需要使⽤⼆级缓存的SqlMapper.xml⽂件中添加配置:<cache />
3. 使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝
4. SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤。
使用⼆级缓存:上面的4个条件缺一不可

pojo类实现 Serializable接口:

public class Car implements Serializable {
CarMapper接口:
    //测试二级缓存
    Car selectById2(Long id);
CarMapper.xml:
<!--默认情况下二级缓存机制是开启的
    只需要对应的SqlMapper.xml文件中添加以下标签。用来表示“我”使用该二级缓存
-->
    <cache/>

    <select id="selectById2" resultType="Car">
        select * from t_car where id=#{id}
    </select>
CarMapperTest:
    @Test
    public void testSelectById2() throws Exception {
        //这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);

        //这行代码结束之后,实际上数据是缓存到一级缓存当中了(sqlSession1是一级缓存)
        Car car1 = mapper1.selectById2(2L);
        System.out.println(car1);

        //如果这里不关闭sqlSession1对象的话,二级缓存当中还是没有数据
        sqlSession1.close();

        //这行代码结束之后,实际上数据是缓存到一级缓存当中了(sqlSession2是一级缓存)
        Car car2 = mapper2.selectById2(2L);
        System.out.println(car2);

        //这里关闭sqlSession2对象,会将sqlSession2这个一级缓存中的数据写入二级缓存当中
        sqlSession2.close();
        //这里关闭sqlSession1对象,会将sqlSession1这个一级缓存中的数据写入二级缓存当中
//        sqlSession1.close();

    }
⼆级缓存的失效:只要两次查询之间出现了增删改操作。⼆级缓存就会失效。【⼀级缓存也会失效】
⼆级缓存的相关配置:

1. eviction:指定从缓存中移除某个对象的淘汰算法。默认采⽤LRU策略。
        a. LRU:Least Recently Used。最近最少使⽤。优先淘汰在间隔时间内使⽤频率最低的对象。(其实还有⼀种淘汰算法LFU,最不常⽤。)
        b. FIFO:First In First Out。⼀种先进先出的数据缓存器。先进⼊⼆级缓存的对象最先被淘汰。
        c. SOFT:软引⽤。淘汰软引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
        d. WEAK:弱引⽤。淘汰弱引⽤指向的对象。具体算法和JVM的垃圾回收算法有关。
2. flushInterval:
        a. ⼆级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存⾜够⼤,⼀直会向⼆级缓存中缓存数据。除⾮执⾏了增删改。
3. readOnly
        a. true:多条相同的sql语句执⾏之后返回的对象是共享的同⼀个。性能好。但是多线程并发可能会存在安全问题。
        b. false:多条相同的sql语句执⾏之后返回的对象是副本,调⽤了clone⽅法。性能⼀般。但安全。
4. size:
        a. 设置⼆级缓存中最多可存储的java对象数量。默认值 1024

14.3 MyBatis集成EhCache

集成EhCache是为了代替mybatis⾃带的⼆级缓存。⼀级缓存是⽆法替代的。
mybatis对外提供了接⼝,也可以集成第三⽅的缓存组件。⽐如EhCache、Memcache等。都可以。
EhCache是Java写的。Memcache是C语⾔写的。所以mybatis集成EhCache较为常⻅,按照以下步骤操作,就可以完成集成:
第⼀步:引⼊mybatis整合ehcache的依赖。
<!--mybatis集成ehcache的组件-->
<dependency>
     <groupId>org.mybatis.caches</groupId>
     <artifactId>mybatis-ehcache</artifactId>
     <version>1.2.2</version>
</dependency>
<!--ehcache需要slf4j的⽇志组件,log4j不好使-->
<dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.2.11</version>
     <scope>test</scope>
</dependency>
第⼆步:在类的根路径下新建echcache.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">
    <!--磁盘存储:将缓存中暂时不使⽤的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
    <diskStore path="e:/ehcache"/>

    <!--defaultCache:默认的管理策略-->
    <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有
   效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
    <!--maxElementsInMemory:在内存中缓存的element的最⼤数⽬-->
    <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
    <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
    <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多⻓时间没有被访问就会失
   效。只对eternal为false的有效。默认值0,表示⼀直可以访问-->
    <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。
   只对eternal为false的有效。默认值0,表示⼀直可以访问-->
    <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
    <!--FIFO:first in first out (先进先出)-->
    <!--LFU:Less Frequently Used (最少使⽤).意思是⼀直以来最少被使⽤的。缓存的元
   素有⼀个hit 属性,hit 值最⼩的将会被清出缓存-->
    <!--LRU:Least Recently Used(最近最少使⽤). (ehcache 默认值).缓存的元素有⼀
   个时间戳,当缓存容量满了,⽽⼜需要腾出地⽅来缓存新的元素的时候,那么现有缓存元素中时间戳
   离当前时间最远的元素将被清出缓存-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>

</ehcache>
第三步:修改SqlMapper.xml⽂件中的<cache/>标签,添加type属性。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

⼗五、MyBatis的逆向⼯程

所谓的逆向⼯程是:根据数据库表逆向⽣成Java的pojo类,SqlMapper.xml⽂件,以及Mapper接⼝类等。
要完成这个⼯作,需要借助别⼈写好的逆向⼯程插件。
思考:使⽤这个插件的话,需要给这个插件配置哪些信息?
pojo类名、包名以及⽣成位置。
SqlMapper.xml⽂件名以及⽣成位置。
Mapper接⼝名以及⽣成位置。
连接数据库的信息。
指定哪些表参与逆向⼯程。
......

15.1 逆向⼯程配置与⽣成

第⼀步:基础环境准备
新建模块:mybatis- 012 -generator1
打包⽅式:jar
第⼆步:在pom中添加逆向⼯程插件
  <!--配置mybatis的逆向工程-->
    <!--定制构建过程-->
    <build>
        <!--可配置多个插件-->
        <plugins>
            <!--其中的⼀个插件:mybatis逆向⼯程插件-->
            <plugin>
                <!--插件的GAV坐标-->
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.1</version>
                <!--允许覆盖-->
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
                <!--插件的依赖-->
                <dependencies>
                    <!--mysql驱动依赖-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.30</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
第三步:配置generatorConfig.xml
该⽂件名必须叫做:generatorConfig.xml
该⽂件必须放在类的根路径下。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
    targetRuntime有两个值:
    MyBatis3Simple:⽣成的是基础版,只有基本的增删改查。
    MyBatis3:⽣成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!--防⽌⽣成重复代码-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>

        <commentGenerator>
            <!--是否去掉⽣成⽇期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/powernode"
                        userId="root"
                        password="r38153">
        </jdbcConnection>

        <!-- ⽣成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启⼦包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空⽩-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- ⽣成SQL映射⽂件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启⼦包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>

        <!-- ⽣成Mapper接⼝的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.powernode.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!-- 表名和对应的实体类名-->
        <table tableName="t_car" domainObjectName="Car"/>
    </context>
</generatorConfiguration>
第四步:运⾏插件

15.2 测试逆向⼯程⽣成的是否好⽤

第⼀步:环境准备
●依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
●jdbc.properties、mybatis-config.xml logback.xml
●utils类
第⼆步:编写测试程序
    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars=mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


    @Test
    public void testDeleteByPrimaryKey(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count =mapper.deleteByPrimaryKey(43L);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

15.3 逆向工程之使用增强版 

新建模块:mybatis-013-generator2

和基础版操作一样,只需改generatorConfig.xml中的

    MyBatis3Simple:⽣成的是基础版,只有基本的增删改查。
    MyBatis3:⽣成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3">

编写测试程序:

    //CarExample类负责封装查询条件的
    @Test
    public void testSelect(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        //执行查询语句
        //1.查询一个
        Car car=mapper.selectByPrimaryKey(1L);
        System.out.println(car);
        //2.查询所有
        List<Car> cars= mapper.selectByExample(null);
        cars.forEach(car1 -> System.out.println(car1));
        System.out.println("=================================");

        //3.按照条件进行查询
        //QBC风格: Query by Criteria 一种查询方式,比较面向对象,看不到sql语句。
        //封装条件,通过CarExample对象来封装查询条件
        CarExample carExample = new CarExample();
        //调用carExample.createCriteria()方法来创建查询条件
        //carExample.createCriteria().andBrandEqualTo("兰博基尼");//相当于 select * from t_car where brand ="兰博基尼"
        //carExample.createCriteria().andBrandLike("比亚迪");//相当于 select * from t_car where brand like "%"#{brand}"%"
        carExample.createCriteria()
                .andBrandLike("比亚迪").
                andGuidePriceGreaterThan(new BigDecimal(20.0));

        //添加or
        carExample.or().andCarTypeEqualTo("燃油车");

        //执行查询
        List<Car> cars2=mapper.selectByExample(carExample);
        cars2.forEach(car2 -> System.out.println(car2));

/*        Car{id=2, carNum='1002', brand='比亚迪唐', guidePrice=55.00, produceTime='2020-11-11', carType='燃油车'}
        Car{id=4, carNum='8888', brand='大众', guidePrice=15.00, produceTime='2023-1-1', carType='燃油车'}
        Car{id=18, carNum='1006', brand='奥迪', guidePrice=40.00, produceTime='2022-03-14', carType='燃油车'}
        Car{id=19, carNum='1111', brand='丰田', guidePrice=10.00, produceTime='2020-11-11', carType='燃油车'}
        Car{id=24, carNum='8888', brand='五菱宏光', guidePrice=30.00, produceTime='2023-4-12', carType='燃油车'}
        Car{id=30, carNum='9991', brand='宝骏', guidePrice=30.00, produceTime='2023-08-01', carType='燃油车'}
        Car{id=31, carNum='1234', brand='别克', guidePrice=15.70, produceTime='2023-05-11', carType='燃油车'}
        Car{id=32, carNum='7777', brand='卓越', guidePrice=50.90, produceTime='2023-8-2', carType='燃油车'}
        Car{id=38, carNum='0456', brand='本田', guidePrice=25.00, produceTime='2023-04-01', carType='燃油车'}*/

        sqlSession.close();
    }

⼗六、MyBatis使⽤PageHelper

16.1 limit分⻚

mysql的limit后⾯两个数字:
   ●第⼀个数字:startIndex(起始下标。下标从0 开始。)
   ●第⼆个数字:pageSize(每⻚显示的记录条数)
假设已知⻚码pageNum,还有每⻚显示的记录条数pageSize,第⼀个数字可以动态的获取吗?
   ●startIndex = (pageNum - 1 ) * pageSize
所以,标准通⽤的mysql分⻚SQL:
select
 *
from
 tableName ......
limit
 (pageNum - 1) * pageSize, pageSize
模块名:mybatis- 014 -page
CarMapper接口:
//分页查询,起始下标,每页显示的记录条数   
 List<Car> selectByPage(@Param("startIndex") int startIndex,@Param("pageSize") int pageSize);
CarMapper.xml:
    <select id="selectByPage" resultType="car">
        select * from t_car limit #{startIndex},#{startIndex}
    </select>
CarMapperTest:
    @Test
    public void testSelectByPage(){
        //现在写的是静态的,将来使用需要从前端页面进行传值
        //获取每页显示的记录条数
        int pageSize=3;
        //显示第几页:页码
        int pageNum=2;
        //计算开始下标
        int startIndex=(pageNum-1)*pageSize;

        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars=mapper.selectByPage(startIndex,pageSize);
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();

    }
获取数据不难,难的是获取分⻚相关的数据⽐较难。可以借助mybatis的PageHelper插件。

16.3 PageHelper插件

使⽤PageHelper插件进⾏分⻚,更加的便捷。

第⼀步:引⼊依赖:
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.3.1</version>
        </dependency>
第⼆步:在mybatis-config.xml⽂件中配置插件:
    <!--    起别名-->
    <typeAliases>
        <package name="com.powernode.mybatis.pojo"/>
    </typeAliases>

    <!--mybatis分页的拦截器-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
第三步:编写Java代码 :
    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);

        //一定要注意:在执行DQL语句之前,开启分页功能
        int pageNum=2;
        int pageSize=5;
        PageHelper.startPage(pageNum,pageSize);

        List<Car> cars=mapper.selectAll();

        //封装分页信息对象new PageInfo()
        //PageInfo对象是PageHelper插件提供的,用来封装分页相关的信息的对象
        PageInfo<Car> carPageInfo = new PageInfo<>(cars, 5);
        System.out.println(carPageInfo);

//        cars.forEach(car -> System.out.println(car));
        //Preparing: select * from t_car LIMIT ?, ?
        sqlSession.close();

        /*
        PageInfo{pageNum=2, pageSize=5, size=5, startRow=6, endRow=10, total=15, pages=3,
        list=Page{count=true, pageNum=2, pageSize=5, startRow=5, endRow=10, total=15, pages=3, reasonable=false, pageSizeZero=false}
        [Car{id=18, carNum='1006', brand='奥迪', guidePrice=40.0, produceTime='2022-03-14', carType='燃油车'},
        Car{id=19, carNum='1111', brand='丰田', guidePrice=10.0, produceTime='2020-11-11', carType='燃油车'},
        Car{id=21, carNum='3333', brand='比亚迪汉', guidePrice=18.0, produceTime='2020-11-1', carType='电车'},
        Car{id=24, carNum='8888', brand='五菱宏光', guidePrice=30.0, produceTime='2023-4-12', carType='燃油车'},
        Car{id=30, carNum='9991', brand='宝骏', guidePrice=30.0, produceTime='2023-08-01', carType='燃油车'}],
        prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
        navigatePages=5, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}

        * */
    }
关键点:
   ●在查询语句之前开启分⻚功能。
   ●在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在⻚⾯上展
示。)

⼗七、MyBatis的注解式开发

mybatis中也提供了注解式开发⽅式,采⽤注解可以减少Sql映射⽂件的配置。
当然,使⽤注解式开发的话,sql语句是写在java程序中的,这种⽅式也会给sql语句的维护带来成本。
官⽅是这么说的:
        使⽤注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂⼀点的语句,Java 注解不仅⼒不从⼼,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做⼀些很复杂的操作,最好⽤ XML 来映射语句。
使⽤注解编写复杂的SQL是这样的:
原则:简单sql可以注解。复杂sql使⽤xml。
模块名:mybatis- 015 -annotation
打包⽅式:jar
依赖:mybatis,mysql驱动,junit,logback
配置⽂件:jdbc.properties、mybatis-config.xml、logback.xml
pojo:com.powernode.mybatis.pojo.Car
mapper接⼝:com.powernode.mybatis.mapper.CarMapper

17.1 @Insert

CarMapper接口:

    @Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
    int insert(Car car);

CarMapperTest类:

    @Test
    public void testInsert(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null,"6666","帕萨特",78.9,"2023-04-12","电车");
        int count=mapper.insert(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

17.2 @Delete

CarMapper接口:

    @Delete("delete from t_car where id=#{id}")
    int deleteById(Long id);

CarMapperTest类:

    @Test
    public void testDeleteById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        mapper.deleteById(45L);
        sqlSession.commit();
        sqlSession.close();
    }

17.3 @Update

CarMapper接口:

    @Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
    int update(Car car);

​​​​​​​CarMapperTest类:

    @Test
    public void testUpdate(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(24L,"8888","金杯",34.44,"2023-03-13","新能源");
        int count =mapper.update(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

17.4 @Select

CarMapper接口:

    //不使用驼峰命名自动映射的,当表当中的字段和类中的字段不匹配时,使用@Results注解
    @Select("select * from t_car where id=#{id}")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "carNum",column = "car_num"),
            @Result(property = "brand",column = "brand"),
            @Result(property = "guidePrice",column = "guide_price"),
            @Result(property = "produce_time",column = "produceTime"),
            @Result(property = "carType",column = "car_time"),
    })
    Car selectById(Long id);

​​​​​​​CarMapperTest类:

    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(24L);
        System.out.println(car);
        sqlSession.close();
    }

​​​​​​​​​​​​​​

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值