MyBatis:MyBatis基础,动态SQL,关联查询,MyBatis一二级缓存机制,ehcache,MyBatis整合Spring、SpringMVC,MyBatis逆向工程,MyBatis插件

1 MyBatis简介

1 简介

MyBatis半自动化的ORM框架

Hibernate全自动的ORM框架

ORM:Object(JavaBean) Relation(关系:和数据库记录的关系)Mapping(数据库的记录和JavaBean对应)

1 HelloWorld(MyBatis操作数据库的基本环节)

1 创建数据表

2 JavaBean类

3 编写dao接口

4 MyBatis操作数据库★

1)导包

mybatis-3.2.8.jar

mysql-connector-java-5.1.37-bin.jar//数据库驱动

log4j.jar

注:log4j依赖配置文件。 这个配置文件可以是xml,也可以是properties文件。但是要求名字必须是log4j。必须放在类路径下(直接放在源码包下)

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

 <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">

<param name="Encoding" value="UTF-8" />

<layout class="org.apache.log4j.PatternLayout">

 <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />

</layout>

 </appender>

 <logger name="java.sql">

<level value="debug" />

 </logger>

 <logger name="org.apache.ibatis">

<level value="debug" />

 </logger>

 <root>

<level value="debug" />

<appender-ref ref="STDOUT" />

 </root>

</log4j:configuration>

2)编写myBatis全局配置文件

这个配置文件的作用就是用来指导MyBatis如何正常工作。Xml配置文件中包含了对MyBatis系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务范围和控制方式的事务管理器(TransactionManager)。从官方文档复制

注意:如果编写文件时没有提示,就需要导入约束文件。约束文件的URI地址:http://mybatis.org/dtd/mybatis-3-config.dtd。导入步骤:window-perference-xml-xml Catalog

 

【myBatis全局配置文件】

<?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">

<!-- dtd文件是xml文档的约束文件。这个约束文件就是规定这个xml能写什么标签的。 有个这个约束就知道能写什么? 只要这个约束文件的位置被正确指向,就会有提示。 给eclipse配置http://mybatis.org/dtd/mybatis-3-config.dtd约束文件的所在位置 在jar包中的org.apache.ibatis.builder.xml下有,直接拿出来 -->

<configuration>

<environments default="development">

<environment id="development">

<transactionManager type="JDBC" />

<!-- 配置数据源。使用连接池技术。连接池使用mybatis自己内置的连接池 -->

<dataSource type="POOLED">

<property name="driver" value="com.mysql.jdbc.Driver" />

<property name="url" value="jdbc:mysql:///person" />

<property name="username" value="root" />

<property name="password" value="123456" />

</dataSource>

</environment>

</environments>

<!-- 一定要把我们写好的SQL映射配置文件 ,注册进MyBatis全局配置中 -->

<mappers>

<!-- 每一个 mapper标签就是注册一个配置文件:resource:是指定从类路径下开始的一个文件路径 -->

<mapper resource="mybatis/mapper/PersonDao.xml"/>

</mappers>

</configuration>

  1. 编写SQL映射文件。

这个文件的作用就是PersonDao的实现文件,相当于PersonDaoImpl实现类。从官方文档复制

注:这个配置文件要有提示,也需要导入约束文件。约束文件的URI:http://mybatis.org/dtd/mybatis-3-mapper.dtd

 

 

【编写SQL映射文件】

<?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">

<!-- 这个文件的作用就是相当于对PersonDao的每一个方法的实现namespace:必须是接口的全类名,这样MyBatis才知道这个配置文件是相当于对哪个接口的实现 -->

<mapper namespace="dao.PersonDao">

<!-- public void savePerson(Person person); -->

<!--定义一个插入操作id="":一定要是方法名parameterType:指定这个方法的传入的参数类型:写类型全类名 -->

<insert id="savePerson" parameterType="bean.Person">

<!-- 标签体内直接写SQL语句即可,SQL的分号一般不要携带 #{属性名}:取出传入的对象的这个属性的值-->

INSERT INTO tal_person(NAME,age,birth,registerTime,salary)

VALUES(#{name},#{age},#{birth},#{registerTime},#{salary})

</insert>

<update id="updatePerson" parameterType="bean.Person">

UPDATE tal_person SET

NAME=#{name},age=#{age},birth=#{birth},

registerTime=#{registerTime},salary=#{salary}

WHERE id=#{id}

</update>

<delete id="deletePerson" parameterType="Integer">

DELETE FROM tal_person WHERE id=#{id}

</delete>

<select id="getPersonById" parameterType="Integer" resultType="bean.Person">

SELECT * FROM tal_person WHERE id=#{id}

</select>

<select id="getAll" resultType="bean.Person">

SELECT * FROM tal_person

</select>

</mapper>

5 测试

public class MyBatisTest {

SqlSessionFactory sessionFactory = null;

//该方法在所有单元测试方法,前运行

@Before

public void getSession() throws IOException {

String resource = "mybatis-config.xml";

// 从类路径下获取这个文件的流

InputStream stream = Resources.getResourceAsStream(resource);

// 根据配置文件的流,使用SqlSessionFactory的建造器创建一个sqlsessionFactory对象

sessionFactory = new SqlSessionFactoryBuilder().build(stream);

}

// 根据id获取

@Test

public void testDelete() {

// 打开一次数据库连接的会话

SqlSession session = sessionFactory.openSession();

try {

// 执行更新操作

// 获取映射

PersonDao personDao = session.getMapper(PersonDao.class);

personDao.deletePerson(3);

session.commit();

} finally {

session.close();

}

}

// 获取所有

@Test

public void testGetAll() {

// 打开一次数据库连接的会话

SqlSession session = sessionFactory.openSession();

try {

// 执行更新操作

// 获取映射

PersonDao personDao = session.getMapper(PersonDao.class);

List<Person> all = personDao.getAll();

for (Person person : all) {

System.out.println(person);

}

session.commit();

} finally {

session.close();

}

}

// 根据id获取

@Test

public void testQueryById() {

// 打开一次数据库连接的会话

SqlSession session = sessionFactory.openSession();

try {

// 执行更新操作

// 获取映射

PersonDao personDao = session.getMapper(PersonDao.class);

Person person = personDao.getPersonById(2);

System.out.println(person);

session.commit();

} finally {

session.close();

}

}

// 更新操作

@Test

public void testUpdate() {

// 打开一次数据库连接的会话

SqlSession session = sessionFactory.openSession();

try {

// 执行更新操作

// 获取映射

PersonDao personDao = session.getMapper(PersonDao.class);

Person person = personDao.getPersonById(2);

person.setName("大圣归来");

personDao.updatePerson(person);

session.commit();

} finally {

session.close();

}

}

@Test

public void test() throws IOException {

// 3、SqlSessionFactory:一定是用来创建SqlSession的

// SqlSession:和数据库的一次会话。类似于我们的Connection;

// 4、使用SqlSessionFactory开启和数据库的一次会话

SqlSession session = sessionFactory.openSession();

// 5、SqlSession可以完成完整的增删改查

// 6、获取到接口是动态代理

try {

PersonDao dao = session.getMapper(PersonDao.class);

Person person = new Person();

person.setAge(20);

person.setName("张三丰");

person.setSalary(298.98);

person.setBirth(new Date());

// 7、保存

dao.savePerson(person);

// 8、提交会话,增删改才能生效

session.commit();

} catch (Exception e) {

e.printStackTrace();

} finally {

// 9、关闭会话。

session.close();

}

}

}

2 MyBatis全局配置文件详解

全局配置文件中的标签顺序是固定的。

<?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>

<!--标签之间的先后顺序一定要注意Content Model : (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?)  -->

 <!-- 这个配置文件指导MyBatis正常执行 -->

 <!-- 1、这个标签是用来进行属性声明或者引用 resource:指定要引用的配置文件(properties)的资源文件位置。从类路径下 url:如果我们配置文件需要请求一个网络地址:http://www.atguigu.com/mybatis/xx.xml 如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载: 1)、在 properties 元素体内指定的属性首先被读取。

 2)、然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。

 3)、最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。 标签体内的property优先级最低==》配置文件的内容==》方法的参数 -->

 <properties resource="dbconfig.properties">

 <!--声明一些变量 -->

 <property name="username" value="root123" />

 </properties>

<!-- 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 -->

 <settings>

 <!-- 关闭了全局缓存 -->

 <setting name="cacheEnabled" value="false" />

 </settings>

 <!--别名处理器: MyBatis运行为我们引用的javaBean类型起一个别名。以后就可以直接简单使用别名了 -->

 <typeAliases>

 <!-- typeAlias:每一个这个标签就是为一个类型起别名 type="com.atguigu.bean.Person":类型全类名 alias="":为这个类型指定一个新的别名。别名=使用的时候不区分大小写 注意:MyBatis已经为我们常见的类型起好了别名。我们自定义别名的时候千万不要占用人家的 -->

 <!-- <typeAlias type="com.atguigu.bean.Person" alias="person"/> -->

 <!-- 批量起别名 -->

 <!--这个包下所有的类都会有一个别名,,默认就是简单类名 -->

 <package name="com.atguigu.bean" />

 </typeAliases>

 <!--类型处理器:MyBatis在底层也是原生的jdbc操作。 MyBatis使用类型处理器来完成对应的java类到数据库类型的映射。 -->

 <!--如果类型处理器不够,我们自己编写一个类型处理器注册进来 -->

 <!-- 真要自己写:实现 org.apache.ibatis.type.TypeHandler 接口 handler="":指定自定义类型处理器的全类名 -->

 <!-- <typeHandlers> <typeHandler handler=""/> </typeHandlers> -->

 <!--我们可以编写一个自己的对象工厂,让MyBatis创建对象的时候利用我们的对象工厂创建对象,而不是自己默认的反射机制 -->

 <!-- 可以实现ObjectFactory接口。或者继承extends DefaultObjectFactory -->

 <!-- <objectFactory type=""></objectFactory> -->

 <!-- 插件: 我们可以为MyBatis定制插件:就是一个拦截器。 可以在MyBatis运行的关键位置拦截一道。 Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ParameterHandler (getParameterObject, setParameters) ResultSetHandler (handleResultSets, handleOutputParameters) StatementHandler (prepare, parameterize, batch, update, query) 如果要写插件: 1)、我们自定义插件实现Interceptor接口。 2)、在我们自定义插件的类上标注注解。作用:为了告诉MyBatis这个插件拦截哪个对象的哪个方法 -->

 <!-- <plugins> <plugin interceptor="插件全类名"></plugin> </plugins> -->

 <!-- environments:所有的环境配置在这里 default:默认使用哪个环境 -->

 <environments default="development">

 <environment id="product">

 <!-- 配置事务管理器 type="": JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。

 MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接, 而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。

 默认情况下它会关闭连接,然而一些容器并不希望这样, 因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。

 自定义: 1)、自定义事务管理器需要实现TransactionFactory接口 2)、type="":自定义事务管理器的全类名 -->

 <transactionManager type="JDBC"></transactionManager>

 <!-- 配置数据源 -->

 <!-- type="": type=”[UNPOOLED|POOLED|JNDI]”) JNDI:Java Named Directory Interface 自定义: 1)、自定义数据源实现org.apache.ibatis.datasource.DataSourceFactory接口 2)、type="":自定义的全类名 -->

 <dataSource type="POOLED">

 <property name="driver"value="${driver}" />

 <property name="url"value="jdbc:mysql://225.23.45.123:3306/test" />

 <property name="username"value="${username}" />

 <property name="password"value="${password}" />

 </dataSource>

 </environment>

 <!-- environment:一个标签就是配置一种环境 .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>

 <!--考虑数据库的移植性  -->

 <!-- <databaseIdProvider type="DB_VENDOR">

 <property name="SQL Server" value="sqlserver" />

 <property name="DB2" value="db2" />

 <property name="Oracle" value="oracle" />

 </databaseIdProvider> -->

 <mappers>

 <!-- 注册xml文件版的 -->

 <!-- url="":配置文件存在于网络的某个位置 -->

 <!-- <mapperurl="http://www.atguigu.com/EmpMapper.xml"/> -->

 <!-- resource:引用类路径下的资源 -->

 <!-- <mapper resource="mybatis/mapper/PersonDao.xml" /> -->

 <!-- class: 引入注解版的dao接口;全类名 -->

 <!-- <mapper class="com.atguigu.dao.PersonDaoAnnotation" /> -->

 <!-- 批量注册:必须满足指定的规则; 1)、文件名必须和接口名一模一样 2)、文件和接口必需放在和接口同一个包下 -->

 <package name="com.atguigu.dao"/>

 </mappers>

</configuration>

3 SQL映射文件详解

<?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">

<!--public interface PersonDao {} -->

<mapper namespace="com.atguigu.dao.PersonDao">

 <!--public void savePerson(Person person); 

  员工插入:id是自增的。获取刚才插入的员工的id

  1)、获取自增主键封装到javaBean

  useGeneratedKeys="true":使用自动生成主键

  keyProperty:指定哪个属性是用来接受生成的主键的值。

  2)、->

 <insert id="savePerson" parameterType="Person"  useGeneratedKeys="true" keyProperty="id">

  <!--order="AFTER":当前这个查询是否在主sql之前运行 

 keyProperty="id"指定哪个属性封装主键值

  resultType="Integer":指定主键的类型    -->

  <!-- <selectKey order="BEFORE" keyProperty="id" resultType="Integer">

  select crm_seq.nextval from dual

  </selectKey> -->

  INSERT INTO tbl_person(NAME,age,birth,registerTime,salary)

 VALUES(#{name},#{age},#{birth},#{registerTime},#{salary})

 </insert>

 <!-- public void delPerson(Integer id,String name); -->

 <!--传入参数:parameterType指定

  1)、简单的参数:

  delPerson(Integer id);

  多个参数的情况下,第一个参数是param1...paramN

  多个参数的情况下,我们推荐使用命名参数。@Param("id")Integer id为参数起名

  2)、传入POJO:

  两种取值方式

  #{对象的属性};进行sql预编译,参数作为占位符传入,安全的

  sql:DELETE FROM tbl_person WHERE id=? AND name=?

  参数:10(Integer), admin666(String)

  更新

  ${对象的属性};直接将拿到的值进行拼串,不能防止sql注入的安全问题

  sql:DELETE FROM tbl_person WHERE id=11 AND name="admin66"

  sql注入问题。

  以后正常情况都使用#{}进行取值,能防止sql注入

  3)、传入map:

  4)、口袋POJO:

  查询Person对象的时候。需要person.name  需要地址Address.street

  MyQueryParam{

  Person person;

  Address address;

  }

  SELECT * FROM tbl_person WHERE id=#{person.id} AND street=#{address.street}

   -->

 <!-- public Person getPersonByIdParaMap(Map<String, Object> map); -->

 <select id="getPersonByIdParaMap"resultType="Person"

 parameterType="map">

 <!--#{key}:从map中取出这个key的值  -->

 SELECT * FROM tbl_person WHERE id=#{id}

 </select>

  <!-- delPersonByCondition -->

  <delete id="delPersonByCondition"parameterType="Person">

  DELETE FROM tbl_person WHERE id=${id} AND name="${name}"

  </delete>

  <!--public void delPersonByName(Integer id,String name);  -->

  <delete id="delPersonByName">

   DELETE FROM tbl_person WHERE id=#{id} AND name=#{name}

  </delete>

 <delete id="delPerson" parameterType="Integer">

  DELETE FROM tbl_person WHERE id=#{id}

 </delete>

 <!-- public void updatePerson(Person person); -->

 <update id="updatePerson" parameterType="Person">

 UPDATE tbl_person set

 name=#{name},age=#{age},birth=#{birth},

 registerTime=#{registerTime},salary=#{salary}

 WHERE id=#{id}

 </update>

 <!-- public Person getPersonById(Integer id);

  resultType:定义返回值的全类型名 -->

 <select id="getPersonById" parameterType="Integer"resultType="Person">

  SELECT * FROM tbl_person WHERE id=#{id}

 </select>

 <!-- public List<Person> getAll();

  返回集合的时候,MyBatis能判断出这、是一个集合,

  resultType只需要定义集合里面的元素类型即可 -->

<!--resultType="person":指定返回值类型的;

 1)、返回值是单个对象。resultType=POJO类型

 2)、返回值是个集合。resultType=集合里面元素的类型(POJO类型) -->

 <select id="getAll" resultType="person">

  SELECT * FROM tbl_person

 </select>

 <!--public Map<String, Object> getPersonByIdResultMap(Integer id);  -->

 <!--  封装map的时候,会自动过滤掉值为null的列-->

 <select id="getPersonByIdResultMap"parameterType="Integer"

  resultType="map">

  SELECT * FROM tbl_person WHERE id=#{id}

 </select>

 <!--public Map<Integer, Person> getAllMap(); 

  resultType:集合里面元素的类型。元素值数据库记录要封装成的对象-->

 <select id="getAllMap" resultType="Person">

  SELECT * FROM tbl_person

 </select>

</mapper>

4 用注解的方式代替SQL映射文件

可以在PersonDao中的方法上加注解的方式代替SQL映射文件。

【MyBatis配置文件写法】

<mappers>

<!-- 注册,类的全类名 -->

<mapper class="com.atguigu.dao.PersonDaoAnnotation"/>

</mappers>

【PersonDao示例】

public interface PersonDaoAnnotation {

@Insert("INSERT INTO tbl_person(NAME,age,birth,registerTime,salary) "

+ "VALUES(#{name},#{age},#{birth},#{registerTime},#{salary})")

public void savePerson(Person person);

@Delete("DELETE FROM tbl_person WHERE id=#{id}")

public void delPerson(Integer id);

@Update("UPDATE tbl_person set "

+ "name=#{name},age=#{age},birth=#{birth},"

+ "registerTime=#{registerTime},salary=#{salary} "

+ "WHERE id=#{id}")

public void updatePerson(Person person);

@Select("SELECT * FROM tbl_person WHERE id=#{id}")

public Person getPersonById(Integer id);

@Select("SELECT * FROM tbl_person")

public List<Person> getAll();

}

2 动态SQL

1 if/where/trim

1 接口方法

public List<Student> getStudentByConditionIf(Student student);

2 SQL映射文件

<!-- person中哪些属性有值,就按照哪个条件查询,如果多个都有就查询满足所有条件的记录-->

<select id="getStudentByConditionIf" parameterType="Student" resultType="Student">

SELECT * FROM tbl_student

<!-- 一般把查询条件,写在where标签中 -->

<where>

<!--自定义字符串的截取策略。trim里面的字符串最终作为一个整体按照trim规则截取,trim体是空的情况下不会加前缀的 prefix

prefixOverrides:指定的字符串作为整体前缀的时候会被删除掉

suffix: trim里面的字符串整体加一个后缀

suffixOverrides: 指定的字符串作为整体后缀的时候会被删除掉 -->

<trim prefixOverrides="AND | OR" suffixOverrides="AND | OR">

<if test="age != null and age>0">

age > #{age} AND

</if>

<!-- && 相当于&& 或者 and -->

<if test="name != null &&!name.equals("") ">

name LIKE CONCAT('%',#{name},'%') AND

</if>

<if test="id != null">

id=#{id} AND

</if>

</trim>

</where>

</select>

2 choose/when/otherwise

1接口方法

public List<Student> getStudentByConditionChoose(Student student);

2 SQL映射

<!-- 需求:如果带了id用id查,没带id看是否带了name,带了name用name查。 最终只会有一个条件会进入。 -->

<!-- public List<Student> getStudentByConditionChoose(Student studsent); -->

<select id="getStudentByConditionChoose" parameterType="Student" resultType="Student">

SELECT * FROM tbl_student

<where>

<choose>

<when test="id != null">

id=#{id}

</when>

<when test="name != null and !name.equals("")">

name=#{name}

</when>

<otherwise>

 id=1;

</otherwise>

</choose>

</where>

</select>

3 foreach

相当于sql语句中的IN查询

1 接口方法

public List<Student> getStudentByForeach(@Param("ids")List<Integer> ids);

2 SQL映射

<select id="getStudentByForeach" parameterType="list" resultType="Student">

SELECT * FROM tbl_student

<where>

<!-- collection:指定要遍历的集合。

 item:遍历出的元素赋值给item

 open:以什么开始

 close:以什么结束

 separator:指定遍历出的元素 以什么分隔-->

<foreach collection="ids" item="id" open="id in(" close=")" separator=",">

#{id}

</foreach>

</where>

</select>

4 set

用于更新

set动态设置set关键字

Set可以自动删除多余的英文逗号

1 接口方法

public void updateStudent(Student student);

2 SQL映射

<update id="updateStudent" parameterType="Student">

UPDATE tbl_student

<!-- 动态设置set关键字 去除多余的逗号 -->

<set>

<if test="name !=null">

NAME=#{name},

</if>

<if test="score>0">

score=#{score},

</if>

<if test="birth !=null">

birth=#{birth},

</if>

<if test="age>0">

age=#{age}

</if>

WHERE id=#{id}

</set>

</update>

3 关联查询

1 一对一

1 连接查询

接口方法:

public Lock getLockById(Integer id);

SQL映射:

<resultMap type="Lock" id="lockResult">

<id column="id" property="id"/>

<result column="lockName" property="lockName"/>

<association property="key" javaType="Key">

<id column="id" property="id"/>

<result column="keyName" property="keyName"/>

</association>

</resultMap>

<select id="getLockById" parameterType="integer" resultMap="lockResult">

SELECT * FROM tbl_lock,tbl_key WHERE tbl_lock.id=#{id} and tbl_lock.key_id=tbl_key.id

</select>

2 分段查询

接口方法:

public Lock getLockByStep(Integer id);

public Key getKeyById(Integer id);

SQL映射:

<resultMap type="Lock" id="my_lock">

<id column="id" property="id"/>

<result column="lockName" property="lockName"/>

<!-- select:声明这个property是通过select指定的sql查询得到的-->

<association property="key" column="key_id" select="dao.KeyDao.getKeyById"></association>

</resultMap>

<select id="getLockByStep" parameterType="integer" resultMap="my_lock">

SELECT * FROM tbl_lock WHERE id=#{id};

</select>

3 延时加载

Lock中的key默认每次查询Lock时,key一定根据select指定的sql语句查询,并封装好。但是有时候我们不需要key对象,这样会浪费资源,所以希望在我们用到key时再去查。

延时加载步骤:

1 导入cglib

cglib-2.2.2.jar

asm-3.3.1.jar

2 在myBatis全局配置文件中开启延迟加载策略

 

<settings>

 <setting name="lazyLoadingEnabled"value="true"/>

 <!-- 属性按需加载 ,这个功能使必须cglib支持的。-->

<setting name="aggressiveLazyLoading" value="false"/>

</settings>

2 一对多

1 员工有一个部门属性

接口方法:

public Employee getEmpById(Integer id);

public Department getDeptById(Integer id);

SQL映射:

<resultMap type="Employee" id="my_emp">

<id column="id" property="id"/>

<result column="name" property="name"/>

<association property="dept"

select="dao.DeptDao.getDeptById"

column="deptId"/>

</resultMap>

<select id="getEmpById" parameterType="integer" resultMap="my_emp">

SELECT * FROM tbl_emp WHERE id=#{id}

</select>

2 部门都多个员工

接口方法:

public Department getDeptById(Integer id);

public Employee getEmpById(Integer id);

SQL映射:

<resultMap type="Department" id="my_dept">

<id column="id" property="id"/>

<result column="deptName" property="deptName"/>

<result column="locAdd" property="locAdd"/>

<!-- emps是一个集合 -->

<collection property="emps"

select="dao.EmpDao.getEmpByDeptId"

column="id">

</collection>

</resultMap>

<select id="getDeptById" parameterType="integer" resultMap="my_dept">

SELECT * FROM tbl_dept WHERE id=#{id}

</select>

4 缓存机制

1 一级默认缓存机制

1 默认

默认情况下,mybatis是启用一级缓存的,是SQLSession级别的,即同一个SQLSession接口对象调用相同的select语句。

默认情况下,select使用缓存,增删改不使用缓存。

当SQLSession flush(刷新)close(关闭)后,该session中的所以Cache将全部清空。

2 一级缓存失效的情况

在底层,缓存保存时,使用方法的签名+hashCode+查询sql的id+查询的参数

1 同一个SQLSession,查询条件不同时。因为在底层,所以还会去数据库查询。

2 SQLSession不同时。即使查询条件一样,也会再次发sql查询。

3 同一个SQLSession,查询条件一样。两次查询期间,执行任何一次增删改操作,也会重新查询。

4 同一个SQLSession,手动的清除缓存:session.clearCache()。

2 二级缓存机制

1 二级机制原理

第一次查出的数据会被放在一级缓存中。只有关闭了SQLSession后,一级缓存中的所有数据会同步到二级缓存中。

查询时,会先去二级缓存中看,如果二级缓存中没有,再去一级缓存中,一级缓存中没有,就查询数据库了。

2 开启二级缓存

1 开启全局二级缓存。在mybatis全局配置文件中配置。

<settings>

<!-- 开启全局缓存 -->

<setting name="cacheEnabled" value="true"/>

<setting name="lazyLoadingEnabled" value="true"/>

<setting name="aggressiveLazyLoading" value="false"/>

</settings>

2 SQL映射中配置缓存策略

<!-- 开启二级缓存

eviction:回收策略。

可用的收回策略有:默认的是 LRU。

LRU – 最近最少使用的:移除最长时间不被使用的对象。

FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。

WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。

false可读写的缓存:会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

true:如果从缓存中获取数据,数据的引用会直接交出去。(外界一但一修改缓存里面的东西也就改了),不安全,速度快。

type:如果是默认的type不用写,自定义的必须写自定义cache全类名,实现MyBatis提供的Cache接口

-->

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"></cache>

3 bean必须实现序列化接口

 public class User implements Serializable

3 整合第三方缓存ehcache

ehcache是一个强大的缓存框架,我们可以使用ehcache来替代mybatis的二级缓存。

1 导包

 ehcache-core-2.6.8.jar //ehcache官方核心包

 mybatis-ehcache-1.0.3.jar //ehcache与mybatis的整合包

 slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar // ehcache要用的日志包

2 编写配置文件

1 配置文件名字必须是ehcache.xml。放在类路径下

2 内容:

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

 <!-- 磁盘保存路径 -->

 <diskStore path="D:\44\ehcache" />

 <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU">

 </defaultCache>

</ehcache>

<!--

属性说明:

 diskStore:指定数据在磁盘中的存储位置。

 defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:

 maxElementsInMemory - 在内存中缓存的element的最大数目

 maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大

 eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断

 overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:

 timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大

 timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大

 diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.

 diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。

 diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作

 memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

 -->

3 SQL映射中配置使用ehcache

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

4 bean不再需要实现序列化接口

4 和缓存有关的设置

1 <setting name="cacheEnabled" value="true"/>

作用:操作二级缓存,value必须是true。一级缓存本来就是默认开启的。

2 每一个sql标签都有一个flushCache 属性。增删改操作默认是true,即刷新缓存。一级二级缓存都会被清除。查询操作默认是false,如果设置为true,会禁用二级缓存,不会禁用一级。

3 select标签的useCache属性。true时,使用缓存。False是只能禁用二级缓存,一级依然开启。因此只有flushCache=”true”可以禁用一级缓存,其他设置不能改变一级缓存。

5 MyBatis&Spring&SpringMVC

1 导包

1 Spring环境

1 Spring核心容器+日志

commons-logging-1.1.3.jar

spring-beans-4.0.0.RELEASE.jar

spring-context-4.0.0.RELEASE.jar

spring-core-4.0.0.RELEASE.jar

spring-expression-4.0.0.RELEASE.jar

2 aop模块

com.springsource.net.sf.cglib-2.2.0.jar

com.springsource.org.aopalliance-1.0.0.jar

com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

spring-aop-4.0.0.RELEASE.jar

spring-aspects-4.0.0.RELEASE.jar

3 数据库模块(只用到声明式事务功能,操作数据库用MyBatis)

spring-jdbc-4.0.0.RELEASE.jar

spring-orm-4.0.0.RELEASE.jar

spring-tx-4.0.0.RELEASE.jar

2 SpringMVC

1 基础模块

spring-web-4.0.0.RELEASE.jar

spring-webmvc-4.0.0.RELEASE.jar

2 文件上传

commons-fileupload-1.2.1.jar

commons-io-2.0.jar

3 jstl标签库

jstl.jar

Standard.jar

4 数据校验(JSR303)

核心校验:

hibernate-validator-5.0.0.CR2.jar

hibernate-validator-annotation-processor-5.0.0.CR2.jar

依赖包:

classmate-0.8.0.jar

jboss-logging-3.1.1.GA.jar

validation-api-1.1.0.CR1.jar

5 Ajax

jackson-annotations-2.1.5.jar

jackson-core-2.1.5.jar

jackson-databind-2.1.5.jar

6 验证码

kaptcha-2.3.2.jar

3 MyBatis

1 MyBatis核心jar包

mybatis-3.2.8.jar

日志:log4j.jar

2 延迟加载功能依赖cglib

asm-3.3.1.jar

cglib-2.2.2.jar

3 MyBatis使用ehcache做二级缓存

ehcache-core-2.6.8.jar

mybatis-ehcache-1.0.3.jar

slf4j-api-1.6.1.jar

slf4j-log4j12-1.6.2.jar

4 数据库驱动、连接池

mysql-connector-java-5.1.37-bin.jar

c3p0-0.9.1.2.jar

2 配置文件

目录结构:

 

1 Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"

xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<context:component-scan base-package="com.atguigu">

<context:exclude-filter type="annotation"

expression="org.springframework.stereotype.Controller" />

<context:exclude-filter type="annotation"

expression="org.springframework.web.bind.annotation.ControllerAdvice" />

</context:component-scan>

<!-- 因为外部数据源配置文件 -->

<context:property-placeholder location="classpath:dbConfig.properties" />

<!-- 配置数据源 -->

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">

<property name="user" value="${jdbc.username}"></property>

<property name="password" value="${jdbc.password}"></property>

<property name="jdbcUrl" value="${jdbc.url}"></property>

<property name="driverClass" value="${jdbc.driver}"></property>

</bean>

<!-- 配置事务控制 MyBatis依靠SqlSessionFactory来创建会话 Spring应该管理SqlSessionFactory,并让其自动创建会话 -->

<!-- mybatis和Spring的整合, 需要一个中间包:mybatis-spring-1.2.2.jar 自动创建映射,自动加载到ioc容器中 -->

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<!-- 指定mybatis的全局配置文件 -->

<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>

<!-- 指定数据源 -->

<property name="dataSource" ref="dataSource"></property>

<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"></property>

<property name="typeAliasesPackage" value="com.atguigu.bean"></property>

</bean>

<!-- 能自动的扫描所有的mapper的实现 -->

<bean id="configure" class="org.mybatis.spring.mapper.MapperScannerConfigurer">

<!-- 这个包下的所有mapper实现自动扫描并且自动加载到ioc容器 -->

<property name="basePackage" value="com.atguigu.dao"></property>

</bean>

<!-- 配置声明式事务 -->

<bean id="transactionManager"

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

<property name="dataSource" ref="dataSource"></property>

</bean>

<!-- 开启基于注解的声明式事务 -->

<tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

2 SpringMVC配置文件

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"

xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<context:component-scan base-package="com.atguigu"

use-default-filters="false">

<context:include-filter type="annotation"

expression="org.springframework.stereotype.Controller" />

<context:include-filter type="annotation"

expression="org.springframework.web.bind.annotation.ControllerAdvice" />

</context:component-scan>

<!-- 配置视图解析器 -->

<bean id="viewResolver"

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix" value="/WEB-INF/pages/"></property>

<property name="suffix" value=".jsp"></property>

</bean>

<!-- 标配 -->

<!-- 动态资源 -->

<mvc:annotation-driven></mvc:annotation-driven>

<!-- 静态资源 -->

<mvc:default-servlet-handler />

</beans>

3 MyBatis配置文件

<?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">

<!-- dtd文件是xml文档的约束文件。这个约束文件就是规定这个xml能写什么标签的。 有个这个约束就知道能写什么? 只要这个约束文件的位置被正确指向,就会有提示。

给eclipse配置http://mybatis.org/dtd/mybatis-3-config.dtd约束文件的所在位置 在jar包中的org.apache.ibatis.builder.xml下有,直接拿出来 -->

<configuration>

<properties resource="dbConfig.properties">

</properties>

<settings>

<!-- 开启全局缓存 -->

<setting name="cacheEnabled" value="true"/>

<setting name="lazyLoadingEnabled" value="true"/>

<setting name="aggressiveLazyLoading" value="false"/>

</settings>

4 log4j配置文件

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

 <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">

<param name="Encoding" value="UTF-8" />

<layout class="org.apache.log4j.PatternLayout">

 <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />

</layout>

 </appender>

 <logger name="java.sql">

<level value="debug" />

 </logger>

 <logger name="org.apache.ibatis">

<level value="debug" />

 </logger>

 <root>

<level value="debug" />

<appender-ref ref="STDOUT" />

 </root>

</log4j:configuration>

5 数据源dbconfig.properties配置文件

jdbc.username=root

jdbc.password=123456

jdbc.url=jdbc:mysql:///person

jdbc.driver=com.mysql.jdbc.Driver

6 ehcache配置文件

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

 xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

 <!-- 磁盘保存路径 -->

 <diskStore path="D:\44\ehcache" />

 <defaultCache

maxElementsInMemory="1000"

maxElementsOnDisk="10000000"

eternal="false"

overflowToDisk="false"

timeToIdleSeconds="120"

timeToLiveSeconds="120"

diskExpiryThreadIntervalSeconds="120"

memoryStoreEvictionPolicy="LRU">

 </defaultCache>

</ehcache>

<!--

属性说明:

 diskStore:指定数据在磁盘中的存储位置。

 defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:

 maxElementsInMemory - 在内存中缓存的element的最大数目

 maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大

 eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断

 overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:

 timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大

 timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大

 diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.

 diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。

 diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作

 memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

 -->

3 测试

1 bean层user.java

public class User{

private Integer id;

private String name;

private Integer age;

2 dao层UserDao.java

public interface UserDao {

public User getUserById(Integer id);

public void addUser(User user);

}

3 dao层的SQL映射文件userMapper.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">

<!-- 这个文件的作用就是相当于对PersonDao的每一个方法的实现 namespace:必须是接口的全类名,这样MyBatis才知道这个配置文件是相当于对哪个接口的实现 -->

<mapper namespace="com.atguigu.dao.UserDao">

<select id="getUserById" parameterType="Integer" resultType="User" >

SELECT * FROM tbl_user WHERE id = #{id}

</select>

<insert id="addUser" parameterType="User">

INSERT INTO tbl_user(NAME,age) VALUES(#{name},#{age})

</insert>

</mapper>

4 service层

@Service

public class UserService {

@Autowired

private UserDao userDao;

public User getUserById(Integer id){

return userDao.getUserById(id);

}

}

5 controller

@Controller

public class UserController {

@Autowired

private UserService userService;

@RequestMapping("/getUser")

public String getUser(@RequestParam("id") Integer id,Map<String,Object> map){

User user = userService.getUserById(id);

map.put("user", user);

return "success";

}

}

6 jsp页面

index.jsp

<a href="getUser?id=1">查询一号用户</a>

Success.jsp

<h1>${user }</h1>

6 MyBatis逆向工程

数据库建表,根据数据表,自动生成javaBean、接口、xml映射文件

MyBatis Generator(MBG):MyBatis代码生成器

使用步骤:

1 导包

mybatis-generator-core-1.3.2.jar

mysql-connector-java-5.1.37-bin.jar//数据库驱动

2 配置文件

<?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>

<!--<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" /> -->

<!-- context:环境

 id:唯一标示

 targetRuntime:运行时环境 ,可选:MyBatis3、MyBatis3Simple-->

 <context id="DB2Tables" targetRuntime="MyBatis3Simple">

 <!-- jdbcConnection:jdbc连接 -->

 <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/person"

 userId="root"

 password="123456">

 </jdbcConnection>

 <javaTypeResolver >

<property name="forceBigDecimals" value="false" />

 </javaTypeResolver>

<!-- 生成Javabean的配置

targetPackage:生成的JavaBean放在哪个包下

targetProject:放在哪个工程下-->

 <javaModelGenerator targetPackage="com.atguigu.bean" targetProject=".\src">

<property name="enableSubPackages" value="true" />

<property name="trimStrings" value="true" />

 </javaModelGenerator>

<!-- sqlMapGenerator:SQL映射文件配置 -->

 <sqlMapGenerator targetPackage="mybatis.mapper" targetProject=".\conf">

<property name="enableSubPackages" value="true" />

 </sqlMapGenerator>

<!-- Java客户端生成:Dao接口 -->

 <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.dao" targetProject=".\src">

<property name="enableSubPackages" value="true" />

 </javaClientGenerator>

 <table tableName="tbl_dept" domainObjectName="Department"></table>

 <table tableName="tbl_user" domainObjectName="User"></table>

 </context>

</generatorConfiguration>

3 测试

public class MBGTest {

public static void main(String[] args) throws Exception {

 List<String> warnings = new ArrayList<String>();

boolean overwrite = true;

//获取配置文件,配置文件在当前工程下

File configFile = new File("mbg.xml");

ConfigurationParser cp = new ConfigurationParser(warnings);

Configuration config = cp.parseConfiguration(configFile);

DefaultShellCallback callback = new DefaultShellCallback(overwrite);

MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);

myBatisGenerator.generate(null);

System.out.println("运行完成");

}

}

7 MyBatis插件

1 分页插件

1 导包

pagehelper-5.0.0.jar

jsqlparser-0.9.5.jar //sql解析工具

2 配置文件

在mybatis-config.xml中,配置插件。

<plugins>

<plugin interceptor="com.github.pagehelper.PageInterceptor">

</plugin>

</plugins>

3 方法调用

public static void main(String[] args) throws Exception {

String resource = "mybatis-config.xml";

InputStream inputStream = Resources.getResourceAsStream(resource);

SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession openSession = builder.openSession();

BookMapper bookMapper = openSession.getMapper(BookMapper.class);

//配置分页

/*

 * 1、导包

 * 2、配置插件

 * 3、调用

 */

Page<Object> page = PageHelper.startPage(7, 5);

List<Book> list = bookMapper.selectAll();

System.out.println("当前页面:"+page.getPageNum());

System.out.println("总页数:"+page.getPages());

System.out.println("总记录数:"+page.getTotal());

System.out.println(list.size());

for (Book book : list) {

System.out.println(book);

}

openSession.close();

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值