Spring+SpringMVC+Mybatis

课前准备工作:

  1. 编程软件:eclipse 2021版本或者相邻版本

  2. 数据库:mysql 5.x.x 5左右的版本即可,太高版本mybatis语句会有所不一样。

  3. jar包下载地址Maven Repository: Search/Browse/Explore

  4. 什么是框架?

一:Mybatis

1、mybatis简介

1.1、 mybatis历史

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。

1.2、MyBatis特性

1) MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

2) MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

3) MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java

Objects,普通的Java对象)映射成数据库中的记录

4) MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

1.3、MyBatis下载

MyBatis下载地址:https://github.com/mybatis/mybatis-3

1.4、 与Java web中的DAO层的对比

1) 需要手动传参数以及使用ResultSet手动获取结果集。

public int addToCart(Cart cart) {
    // TODO Auto-generated method stub
    //设置参数
    String sql = "insert into cart values(?,?,?,?,?,?,?,?)";
    //手动传参
    Object[] params =new Object[] {cart.getId(),cart.getGood_id(),cart.getUser_name(),cart.getIntro(),cart.getAmount(),cart.getPrice(),cart.getTotal_price(),cart.getCover()};
    int result = exceuteUpdate(sql, params);
    return result;
  }
@Override
  public List<Goods> findGoodList(Goods goods){
    Goods goosGoods =null;
    //数据库语句
    String sql = "select g.*,t.name as typename from goods g LEFT JOIN type t on g.type_id = t.id  where 1=1";
    //条件拼接 格式太复杂
    if (goods.getName()!=null&&!"".equals(goods.getName().trim())) {
      sql += " and g.name like'%"+goods.getName()+"%'";
    }
    if (goods.getId()!=0) {
      sql += " and g.id='"+goods.getId()+"'";
    }
    if (goods.getType_id()!=0) {
      sql += " and g.type_id='"+goods.getType_id()+"'";
    }
    
    ResultSet rs = querySql(sql);
    List<Goods> list = new ArrayList<>();
    if(rs!=null) {
      try {
        while (rs.next()) {
          //依次手动获取结果集
           goosGoods = new Goods(rs.getInt("id"),rs.getString("typename"),rs.getString("name"), rs.getString("cover"), rs.getString("image1"), rs.getString("image2"), rs.getFloat("price"), rs.getString("intro"), rs.getInt("stock"), rs.getInt ("type_id"));
           list.add(goosGoods);
        }
      } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    } 
    return list;
  }

2、搭建mybatis

2.1 开发环境

1、 mysql 5

2、 Mybatis 3.5.2

注意:

MySQL不同版本的注意事项

1、驱动类driver-class-name

MySQL 5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver

MySQL 8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver

2、连接地址url

MySQL 5版本的url:

jdbc:mysql://localhost:3306/ssm

MySQL 8版本的url:

jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC

否则运行测试用例报告如下错误:

java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or

represents more

2.2 创建web项目导入jar包

mysql-connector-java-5.1.13.jar

mybatis-3.5.2.jar

2.3、创建MyBatis的核心配置文件

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring

之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。

核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息

核心配置文件存放的位置是Java resources/src目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-config.dtd">
​
<configuration>
 <!--数据库配置-->
 <environments default="development">
   <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/buyerapp?characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>   
        </dataSource>
    </environment>
 </environments>
 
 <!-- 引入mybatis的映射文件所在路径  xxx.xml  -->
 <mappers>
 <!--  1.  <mapper resource="com/mapper/AdminMapper.xml"/>-->
 <!--  2.  
     映射文件必须与接口(数据库操作方法接口)在同一个包里
     映射文件名必须与接口名一致   (AdminMapper)
  -->
 <package name="com.mapper"></package>
 </mappers>
</configuration>

2.4、创建mapper接口

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要

提供实现类。

public interface UserMapper {
    
    /**
    * 添加用户信息
    */
    int insertUser();
}

2.5、创建MyBatis的映射文件

1、映射文件的命名规则:

表所对应的实体类的类名+Mapper.xml

例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

因此一个映射文件对应一个实体类,对应一张表的操作

MyBatis映射文件用于编写SQL,访问以及操作表中的数据

MyBatis映射文件存放的位置与mapper接口同一个包里

2、 MyBatis中可以面向接口操作数据,要保证两个一致:

a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致

b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.UserMapper">  <!-- mapper接口中的全类名与namespace一致 -->
  <!--int insertUser();-->
  <insert id="insertUser">
    insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
  </insert>
</mapper>

2.6、测试

public static void main(String[] args) throws IOException {
    // TODO Auto-generated method stub
    //读取MyBatis的核心配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //创建SqlSessionFactoryBuilder对象
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    //通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    //创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
    //SqlSession sqlSession = sqlSessionFactory.openSession();
    //创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //通过代理模式创建UserMapper接口的代理实现类对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    //调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配
    //映射文件中的SQL标签,并执行标签中的SQL语句
    int result = userMapper.insertUser();
    //sqlSession.commit();
    System.out.println("结果:"+result);
  }

2.7、加入log4j日志功能

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

3、优化核心配置文件

3.1优化核心配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
​
  <!-- 引入properties配置文件,以后所有的数据库操作都可以通过 ${key}来读取信息,
        实现配置集合 -->
  <properties resource="jdbc.properties">
  
  </properties>
  
  <!--驼峰命名规则-->
  <settings>
       <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>
 
 <environments default="development">
   <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
        <!--未配置properties时写的数据库连接方式,放在前面讲
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/buyerapp?characterEncoding=utf-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>    --> 
        
        <!-- 写完jdbc.properties后数据库的连接方式 -->
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
 </environments>
 
 <!-- 引入mybatis的映射文件  -->
 <mappers>
 <!--  <mapper resource="com/mapper/AdminMapper.xml"/>-->
 <!-- 
     映射文件必须与接口在同一个包里
     映射文件名必须与接口名一致   (AdminMapper)
  -->
 <package name="com.mapper"></package>
 </mappers>
</configuration>
jdbc.properties文件,与mybatis-config.xml同一目录下

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/buyerapp?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
jdbc.initialPoolSize=5
jdbc.minPoolSize=1
jdbc.maxPoolSize=10

3.2、优化读取配置文件

分装工厂类 SqlSessionUtil

 public static SqlSession getSession() {
    SqlSession sqlSession = null;
    try {
      InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
      
      SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder();
      SqlSessionFactory sqlSessionFactory = sessionFactoryBuilder.build(is);    
      sqlSession=sqlSessionFactory.openSession(true);
      
    } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return sqlSession;
    
  }

读取

    //调用SqlSessionUtil类静态方法
    SqlSession session = SqlSessionUtil.getSession();
    //通过session获取接口实例话对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    int result = userMapper.insertUser();

4、MyBatis的增删改查

4.1 新增

 <!--  int insertUser();  -->
  <insert id="insertUser">
    insert into t_user values(null,'liming','123456','男',25)  
 </insert>

4.2 删除

  <!-- int deleteUser();  -->
  <delete id="deleteUser">
      delete from t_user where id=1
  </delete>

4.3修改

  <!--  int updataUser();  -->
  <update id="updataUser">
     update t_user set userName='czh',userPassword='654321' where id=2  
  </update>

4.4查询

  1. 查询一条数据

      <!--  User selectUser(); -->
      <select id="selectUser" resultType="com.mybatis.pojo.User">
          select * from t_user where id=2
      </select>
  2. 查询多条数据

  <!-- List<User> selectAllUser();   -->
  <select id="selectAllUser" resultType="com.mybatis.pojo.User">
     select * from t_user
  </select>

注意:

1、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射

关系。

resultType:自动映射,用于属性名和表中字段名一致的情况

resultMap:自定义映射,用于一对多或多对一或字段名和属性名不一致的情况

parameterType和 parameterMap与上面情况一致。用于表示参数类型。方法有参数时需要加上

5、MyBatis获取参数值的两种方式

MyBatis获取参数值的两种方式:${}#{}

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引

号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,

可以自动添加单引号

5.1 多种查询情况

  1. 通过一个参数查询一个实体类对象

          Admin getAdminByyym(String username);
    
          <!-- 1.为单个变量时  Admin getAdminByyym(String username);-->
          <select id="getAdminByyym" resultType="com.pojo.Admin">
                <!-- select * from admin where yhm = #{username}   {}里的值是任意的 -->  
                select * from admin where yhm = '${usename}' <!-- 用$必须加单引号 -->
          </select>

    只有一个参数的时候{}里的参数名字随便写,他都能一一对应。

  2. 通过两个及以上的参数查询一个实体类对象

    1. 使用param1、param2.....进行赋值

      //多个参数变量获取值时
      Admin checklogin(String yhm,String mm);
    <!-- 2.为多个变量时  Admin checklogin(String yhm,String mm); -->
    <select id="checklogin" resultType="com.pojo.Admin">
        select * from admin where yhm = #{param1} and mm = #{param2}
    </select>

    使用 #{param1}指定对应的参数为第一个

    1. 使用 @Param给参数命名

      Admin checkLoginByParam(@Param("yhm") String yhm, @Param("mm") String mm);
      <!-- Admin checkLoginByParam(@Param("yhm") String yhm, @Param("mm") String mm); -->
      <select id="checkLoginByParam" resultType="com.pojo.Admin">
          select * from admin where yhm = #{yhm} and mm = #{mm}
      </select>

      #{yhm}里的参数名要跟@Param("yhm")保持一致

    2. 使用map封装参数(推荐使用)

      User selectUserBymap(Map<String, Object> map);
      <!--  User selectUserBymap(Map<String, Object> map); -->
      <select id="selectUserBymap" resultType="com.mybatis.pojo.User" parameterType="java.util.Map">
         select * from t_user where userName = #{userName} and userPassword = #{userPassword}
      </select>  
    Map<String, Object> map = new HashMap<>();
    map.put("userName", "czh");
    map.put("userPassword", "654321");
    
    User user =  userMapper.selectUserBymap(map);
    System.out.println(user);
  1. 查个数

      //1.查询总数
      Integer getcount();
          <!-- Integer getcount(); -->
          <select id="getcount" resultType="Integer">
                 select count(*) from admin
          </select>

  2. 多个参数的添加

      //多个参数添加值时
      void insertAdminByparam(Admin admin);
           <!-- 多个变量添加时  void insertAdminByparam(Admin admin); -->
          <insert id="insertAdminByparam">
              insert into admin values(null,#{yhm},#{mm})
          </insert>
        Admin admin5 =new Admin(null, "1","12");
        adminMapper.insertAdminByparam(admin5); 

    {}里的参数要与数据库表字段一致

      //批量添加多个员工
      void inertMoreAdmin(@Param("admins") List<Admin> admins);
          <!-- void inertMoreAdmin(@Param("admins") List<Admin> admins); -->
          <insert id="inertMoreAdmin">
               insert into admin values
               <foreach collection="admins" item="admins" separator=",">
                   (null,#{admins.yhm},#{admins.mm})
               </foreach>
          </insert>
        //批量添加
        Admin admin7 = new Admin(null,"12","12");
        Admin admin8 = new Admin(null,"12333","12333");
        List<Admin> list2 = new ArrayList<>();
        list2.add(admin7);
        list2.add(admin8);
        adminMapper.inertMoreAdmin(list2);

6、模糊查询

  //模糊查询的使用
  List<User> selectUserByLike(String userName);
  <!--  List<User> selectUserByLike(); -->
  <select id="selectUserByLike" resultType="com.mybatis.pojo.User" parameterType="String" >
      select * from t_user where userName like '%${yhm}%'
  </select>

7、动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了

解决 拼接SQL语句字符串时的痛点问题。

7.1 if

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之

标签中的内容不会执行

  <!--  List<User> userList(User user); -->
  <select id="userList" resultType="com.mybatis.pojo.User" parameterType="com.mybatis.pojo.User">
      select * from t_user where 1=1
      <if test="userName != '' and userName != null">
          and userName like '%${userName}%'
      </if>
      <if test="userPassword != '' and userPassword != null ">
          and userPassword = #{userPassword}
      </if>
      <if test="sex != '' and sex != null ">
          and sex = #{sex}
      </if>
  </select>
    User user2 = new User();
    user2.setUserName("李明");
    user2.setSex("男");
    List<User> usersLists = userMapper.userList(user2);
    System.out.println(usersLists);

7.2 where

where和if一般结合使用:

a>若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字

b>若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的

and去掉

注意:where标签不能去掉条件最后多余的and

  <select id="userList" resultType="com.mybatis.pojo.User" parameterType="com.mybatis.pojo.User">
      select * from t_user
     <where>
        <if test="userName != '' and userName != null">
          userName like '%${userName}%'
      </if>
      <if test="userPassword != '' and userPassword != null ">
          and userPassword = #{userPassword}
      </if>
      <if test="sex != '' and sex != null ">
          and sex = #{sex}
      </if>
     </where>
  </select>

7.3 trim

trim用于去掉或添加标签中的内容

常用属性:

prefix:在trim标签中的内容的前面添加某些内容

prefixOverrides:在trim标签中的内容的前面去掉某些内容

suffix:在trim标签中的内容的后面添加某些内容

suffixOverrides:在trim标签中的内容的后面去掉某些内容

  <select id="userList" resultType="com.mybatis.pojo.User" parameterType="com.mybatis.pojo.User">
    select * from t_user 
    <trim prefix="where" suffixOverrides="and">
      <if test="userName != '' and userName != null">
          userName like '%${userName}%' and 
      </if>
      <if test="userPassword != '' and userPassword != null ">
          userPassword = #{userPassword} and 
      </if>
      <if test="sex != '' and sex != null ">
          sex = #{sex}
      </if>
    </trim>
  </select>
​
​
 <insert id="insertSelective" parameterType="com.buyerApp.system.admin.admin.pojo.Admin">
    insert into admin
    <trim prefix="(" suffix=")" suffixOverrides=",">
      <if test="id != null">
        id,
      </if>
      <if test="yhm != null">
        yhm,
      </if>
      <if test="mm != null">
        mm,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides=",">
      <if test="id != null">
        #{id,jdbcType=INTEGER},
      </if>
      <if test="yhm != null">
        #{yhm,jdbcType=VARCHAR},
      </if>
      <if test="mm != null">
        #{mm,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>

7.4 choose、when、otherwise

choose、when、 otherwise相当于if...else if..else

  <select id="userList" resultType="com.mybatis.pojo.User" parameterType="com.mybatis.pojo.User">
    select * from t_user
    <where>
       <choose>
          <when test="userName != '' and userName != null">
              userName = #{userName}
          </when>
          <when test="userPassword != '' and userPassword != null">
              userPassword = #{userPassword}
          </when>
          <when test="sex != '' and sex != null ">
              sex = #{sex}
          </when>
       </choose>
    </where>
    User user2 = new User();
    user2.setUserName("李明");
    user2.setSex("男");
    List<User> usersLists = userMapper.userList(user2);
    System.out.println(usersLists);

最终结果只满足一个

DEBUG [main] - ==>  Preparing: select * from t_user WHERE userName = ? 
DEBUG [main] - ==> Parameters: 李明(String)
DEBUG [main] - <== Total: 1
[User [id=4, userName=李明, userPassword=123, sex=男, age=25]]

7.5 foreach

循环

<!--int insertMoreEmp(List<Emp> emps);-->
<insert id="insertMoreEmp">
  insert into t_emp values
  <foreach collection="emps" item="emp" separator=",">
    (null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)
  </foreach>
</insert>
​
<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
  delete from t_emp where
  <foreach collection="eids" item="eid" separator="or">
    eid = #{eid}
  </foreach>
</delete>
​
<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
  delete from t_emp where eid in
  <foreach collection="eids" item="eid" separator="," open="(" close=")">
    #{eid}
  </foreach>
</delete>

7.6、SQL片段

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

    <sql id="userColumns">
       userName,userPassword,sex
    </sql>
   select <include refid="userColumns"></include> from t_user

注意:没查到的默认为0 或者 null

7.7 set

用于update语句中

 <update id="updateByPrimaryKeySelective" parameterType="com.buyerApp.system.admin.admin.pojo.Admin">
    update admin
    <set>
      <if test="yhm != null">
        yhm = #{yhm,jdbcType=VARCHAR},
      </if>
      <if test="mm != null">
        mm = #{mm,jdbcType=VARCHAR},
      </if>
    </set>
    where id = #{id,jdbcType=INTEGER}
  </update>

8、MyBatis的逆向工程

8.1.安装插件:

安装逆向工程插件 按照第3种方法按照

8.2 创建generator.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” 会生成简单的增删改查 -->
 <context id="DB2Tables" targetRuntime="MyBatis3">
  <commentGenerator>
   <!--
          suppressAllComments属性值:
          true:自动生成实体类、SQL映射文件时没有注释
          false:自动生成实体类、SQL映射文件,并附有注释
        -->
   <property name="suppressAllComments" value="true" />
  </commentGenerator>
   
  <!-- 数据库连接信息 -->
  <jdbcConnection driverClass="com.mysql.jdbc.Driver"
   connectionURL="jdbc:mysql://localhost:3306/MybatisTest?characterEncoding=UTF-8" 
   userId="root"  password="123456">
  </jdbcConnection>
  
   <!-- 
      forceBigDecimals属性值: 
        true:把数据表中的DECIMAL和NUMERIC类型,
解析为JAVA代码中的java.math.BigDecimal类型 
        false(默认):把数据表中的DECIMAL和NUMERIC类型,
解析为解析为JAVA代码中的Integer类型 
    -->
  <javaTypeResolver>
   <property name="forceBigDecimals" value="false" />
  </javaTypeResolver>
​
    <!-- 
      targetProject属性值:实体类pojo的生成位置  项目名/src
      targetPackage属性值:实体类所在包的路径   包路径名
    --> 
  <javaModelGenerator targetPackage="com.pojo" 
                             targetProject="Mybatis02/src/mybatis">
   <!-- trimStrings属性值:
        true:对数据库的查询结果进行trim操作
        false(默认):不进行trim操作
        -->
   <property name="trimStrings" value="true" />
  </javaModelGenerator>
   
  <!-- 
      targetProject属性值:SQL映射文件的生成位置(写数据库语句类)  
      targetPackage属性值:SQL映射文件所在包的路径(xml)
    -->
  <sqlMapGenerator targetPackage="com.mapper" targetProject="Mybatis02/src/mybatis">
  </sqlMapGenerator>
  <!-- 生成动态代理的接口  -->
  <javaClientGenerator type="XMLMAPPER" targetPackage="com.mapper" targetProject="Mybatis02/src/mybatis">
  </javaClientGenerator>
  
  <!-- 指定要逆向的数据库表  tableName表名   useActualColumnNames true:实体类属性使用原数据库属性名  false:使用驼峰规则-->
  <table tableName="category">
  <property name="useActualColumnNames" value="false"/>
   </table>
      
 </context>
</generatorConfiguration>

9、映射关系

  <resultMap id="BaseResultMap" type="com.mybatis.pojo.TAdmin">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="admin_name" jdbcType="VARCHAR" property="adminName" />
    <result column="admin_password" jdbcType="VARCHAR" property="adminPassword" />
    <result column="admin_email" jdbcType="VARCHAR" property="adminEmail" />
    <result column="admin_address" jdbcType="VARCHAR" property="adminAddress" />
  </resultMap>

参数以实体类的属性名为准,即property的值

注意: 在写select时,必须加上resultMap="BaseResultMap",才能保持数据的一致性

  <select id="selectUserList" resultType="com.ssmTest.system.admin.user.pojo.User" resultMap="BaseResultMap">
     select <include refid="Base_Column_List" /> from `user`
  </select>

9.1、 逆向工程中各个方法的使用

逆向工程提供了数据的增删改查的基本操作

方法功能说明
long countByExample(EmployeeExample example) ;按条件计数
int deleteByExample(EmployeeExample example);按条件删除
int deleteByPrimaryKey(Integer id);按主键删除
int insert(Employee record);插入数据
int insertSelective(Employee record);按条件插入数据
List selectByExample(EmployeeExample example);按条件查询
Employee selectByPrimaryKey(Integer id);按主键查询
int updateByExampleSelective(@Param(“record”) Employee record, @Param(“example”) EmployeeExample example);按条件更新值不为null的字段
int updateByExample(@Param(“record”) Employee record, @Param(“example”) EmployeeExample example);按条件更新
int updateByPrimaryKeySelective(Employee record);按主键更新值不为null的字段
int updateByPrimaryKey(Employee record);按主键更新
9.1.1 Example用法讲解

Example相当于where后面的条件

  1. deleteByExample

        TAdminExample adminExample = new TAdminExample();
        TAdminExample.Criteria criteria = adminExample.createCriteria();
        
        criteria.andAdminNameLike("%李%");
        adminMapper.deleteByExample(adminExample);
  2. selectByExample

        TAdminExample adminExample = new TAdminExample();
        TAdminExample.Criteria criteria = adminExample.createCriteria();
        
        criteria.andAdminNameLike("李%"); 
        //adminMapper.deleteByExample(adminExample);
        criteria.andAdminPasswordEqualTo("123");
        List<TAdmin> admins =  adminMapper.selectByExample(adminExample);
        System.out.println(admins.toString());
    //相当于数据库语句  select id, admin_name, admin_password, admin_email, admin_address from t_admin WHERE ( admin_name like ‘李%’ and admin_password = ‘123’ ) 
  3. countByExample

        TAdminExample adminExample = new TAdminExample();
        TAdminExample.Criteria criteria = adminExample.createCriteria();
        
        criteria.andAdminNameLike("李%");
        //adminMapper.deleteByExample(adminExample);
        criteria.andAdminPasswordEqualTo("123");
        List<TAdmin> admins =  adminMapper.selectByExample(adminExample);
        System.out.println(admins.toString());
        
        long count = adminMapper.countByExample(adminExample);
        System.out.println("姓李的且密码为123的人数有:"+count);
  4. int updateByExampleSelective(@Param("row") TAdmin row, @Param("example") TAdminExample example):

    有条件的修改部分数据。参数 前面是要修改的数据,后面是条件

        TAdminExample adminExample = new TAdminExample();
        TAdminExample.Criteria criteria = adminExample.createCriteria();
        
        criteria.andAdminNameLike("李%");
        criteria.andAdminPasswordEqualTo("123");
    ​
        TAdmin admin = new TAdmin();
        admin.setAdminAddress("中国");
        admin.setAdminEmail("123456@qq.com");
        
        adminMapper.updateByExampleSelective(admin, adminExample);
    ​
    //相当于 update t_admin SET admin_email = ?, admin_address = ? WHERE ( admin_name like ? and admin_password = ? ) 
    Parameters: 123456@qq.com(String), 中国(String), 李%(String), 123(String)
  5. updateByExample:有条件的修改所有数据

10、ZUI前端框架与分页

zui网址

作业: 使用Javaweb+mybatis完成登陆操作,并使用分页功能查询所有的数据。在分页表上有根据名字查询功能,添加功能,修改功能。

二:Spring

1、spring简介

1.1、Spring概述

官网地址:Spring | Home

Spring 是最受欢迎的企业级 Java 应用程序开发框架,数以百万的来自世界各地的开发人员使用

Spring 框架来创建性能好、易于测试、可重用的代码。

Spring 框架是一个开源的 Java 平台,它最初是由 Rod Johnson 编写的,并且于 2003 年 6 月首

次在 Apache 2.0 许可下发布。

Spring 是轻量级的框架,其基础版本只有 2 MB 左右的大小。

Spring 框架的核心特性是可以用于开发任何 Java 应用程序,但是在 Java EE 平台上构建 web 应

用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用,通过启用基于 POJO

编程模型来促进良好的编程实践。

1.2、Spring家族

项目列表:Spring | Projects

1.3、Spring Framework

Spring 基础框架,可以视为 Spring 基础设施,基本上任何其他 Spring 项目都是以 Spring Framework为基础的。

1.3.1、Spring Framework特性
  • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常

小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会

破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序

时结构清晰、简洁优雅。

  • 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源

变成环境将资源准备好,我们享受资源注入。

  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功

能。

  • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化

的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发

效率。

  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML

和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭

建超大型复杂应用系统。

  • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。

  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且

Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基

础上全部使用 Spring 来实现。

1.3.2、Spring Framework五大功能模块
功能模块功能介绍
Core Container核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
AOP&Aspects面向切面编程
Testing提供了对 junit 或 TestNG 测试框架的整合。
Data Access/Integration提供了对数据访问/集成的功能。
Spring MVC提供了面向Web应用程序的集成功能。

2、IOC

2.1、IOC容器

2.1.1、IOC思想

IOC:Inversion of Control,翻译过来是反转控制

①获取资源的传统方式

自己做饭:买菜、洗菜、择菜、炒菜,全过程参与,费时费力,必须清楚了解资源创建整个过程中的全部细节且熟练掌握。

在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的

模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。

②反转控制方式获取资源

点外卖:下单、等、吃,省时省力,不必关心资源创建过程的所有细节。

反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

③DI

DI:Dependency Injection,翻译过来是依赖注入

DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器

的资源注入。相对于IOC而言,这种表述更直接。

所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

2.2、 IOC在spring中的实现

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:

①BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

②ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用

ApplicationContext 而不是底层的 BeanFactory。

2.3基于XML管理bean

2.3.1 创建IOC容器
  1. 创建 applicationContext.xml ,放在src目录底下。

    <?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:tx="http://www.springframework.org/schema/tx" 
      xmlns:aop="http://www.springframework.org/schema/aop" 
      xmlns:context="http://www.springframework.org/schema/context" 
      xsi:schemaLocation="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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
          
        <!--创建bean-->
        <bean id="HelloWorld" class="com.spring.bean.HelloWorld"> </bean>
    </beans>
  2. 获取bean

      public class HelloWorld {
      public void sayHello() {
        System.out.println("Hello Spring");
       }
     }
    ​
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //根据bean的id获取对象
        //HelloWorld helloWorld  = (HelloWorld) ac.getBean("helloWorld");
        //根据类名获取对象  这种情况类只能有一个对象
        //HelloWorld helloWorld = ac.getBean(HelloWorld.class);
        //根据id+类名获取
        HelloWorld helloWorld = ac.getBean("helloWorld", HelloWorld.class);
        helloWorld.sayHello();
2.3.2 依赖注入之setter注入
  <!-- 依赖注入之setter注入 -->
  <bean id="user" class="com.spring.bean.User">
      <property name="username" value="周杰伦"></property>
      <property name="paswd" value="123456"></property>
  </bean>
    User user = ac.getBean(User.class);
    System.out.println(user);
​
//结果:  User [username=周杰伦, paswd=123456]
2.3.3 依赖注入之构造器注入
  <!-- 构造器注入 -->
  <bean id="user2" class="com.spring.bean.User">
     <constructor-arg value="林俊杰"></constructor-arg>
     <constructor-arg value="6789"></constructor-arg>
  </bean>
    User user = (User) ac.getBean("user2");
    System.out.println(user);
    
    //结果  User [username=林俊杰, paswd=6789]

2.4基于注解管理bean

2.4.1 标记与扫描
  1. 编写类。类上需要添加注解@Controller、@Service、@Repository

    @Controller
    public class Admin {
    ​
      public void idAdmin() {
        System.out.println("扫描注入,获取对象");
      }
    }

    @Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

    对于Spring使用IOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这

    三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

    注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。

  2. 添加扫描注入

        <!-- 扫描注入   --> 
      <context:component-scan base-package="com.spring.bean"></context:component-scan>
  3. 测试

        //根据类名
        //Admin admin = (Admin) ac.getBean(Admin.class);
        //根据id
        Admin admin = (Admin) ac.getBean(“admin”);
        admin.idAdmin();
  4. Bean中id的确认方法

    在我们使用XML方式管理bean的时候,每个bean都有一个唯一标识,便于在其他地方引用。现在使用

    注解后,每个组件仍然应该有一个唯一标识。

    默认情况

    类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。

    自定义bean的id

    可通过标识组件的注解的value属性设置自定义的bean的id

    @Service("userService")//默认为userServiceImpl public class UserServiceImpl implements

    UserService {}

2.4.2 基于注解的自动装配
①场景模拟

参考基于xml的自动装配

在UserController中声明UserService对象

在UserServiceImpl中声明UserDao对象

②@Autowired注解

在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。以后我们在项

目中的正式用法就是这样。

@Controller
public class UserController {
    @Autowired
    private UserService userService;
  
    public void saveUser(){
        userService.saveUser();
    }
}
public interface UserService {
    void saveUser();
}
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}
public interface UserDao {
  void saveUser();
} 
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
        System.out.println("数据库保存成功");
    }
}

3、AOP

3.1、场景模拟

3.1.1、声明接口

声明计算器接口Calculator,包含加减乘除的抽象方法

public interface Calculator {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}
3.1.2 、创建实体类
public class CalculatorPureImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}
3.1.3、改变需求

为了提高系统安全,现需要加入日志功能,用户的每一步操作,都需要计入日志里。

public class CalculatorLogImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        System.out.println("[日志] 用户xxx操作 add 方法开始了,参数是:" + i + "," + j);
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] add 方法结束了,结果是:" + result);
        return result;
    }
    @Override
    public int sub(int i, int j) {
        System.out.println("[日志]用户xxx操作 sub 方法开始了,参数是:" + i + "," + j);
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] sub 方法结束了,结果是:" + result);
        return result;
    }
    @Override
    public int mul(int i, int j) {
        System.out.println("[日志]用户xxx操作 mul 方法开始了,参数是:" + i + "," + j);
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] mul 方法结束了,结果是:" + result);
        return result;
    }
    @Override
    public int div(int i, int j) {
        System.out.println("[日志]用户xxx操作 div 方法开始了,参数是:" + i + "," + j);
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        System.out.println("[日志] div 方法结束了,结果是:" + result);
        return result;
    }
}
3.1.4、 提出问题
①现有代码缺陷

针对带日志功能的实现类,我们发现有如下缺陷:

  • 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力

  • 附加功能分散在各个业务功能方法中,不利于统一维护

②解决思路

解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。

③困难

解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。

3.2、AOP概念以及相关术语

3.2.1、概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

①切面

需要添加额外功能的类

②通知

每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

  • 前置通知:在被代理的目标方法执行

  • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝

  • 异常通知:在被代理的目标方法异常结束后执行(死于非命

  • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论

  • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所

③切点

切面的功能插入到哪里去执行的路径。

3.2.2、作用
  • 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。

  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

3.3 基于注解的AOP

3.3.1 配置工作
  1. 需要的依赖

    aspectjrt-1.5.3.jar

    aspectjweaver-1.9.9.jar

    spring-aspects-5.2.22.RELEASE.jar

  2. 配置

    创建 applicationContext3.xml 文件

    <?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:tx="http://www.springframework.org/schema/tx" 
      xmlns:aop="http://www.springframework.org/schema/aop" 
      xmlns:context="http://www.springframework.org/schema/context" 
      xsi:schemaLocation="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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
      
      <!-- 开始基于注解的AOP -->
      <context:component-scan base-package="com.AOP.test"></context:component-scan> 
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      </beans>
    ​
  3. 功能编码

    • 功能接口

      package com.AOP.test;
      ​
      public interface Calculator {
          int add(int i, int j);
          int sub(int i, int j);
          int mul(int i, int j);
          int div(int i, int j);
      }
      ​
      ​
    • 实现类

      package com.AOP.test;
      ​
      import org.aspectj.lang.annotation.Aspect;
      import org.springframework.context.annotation.EnableAspectJAutoProxy;
      import org.springframework.stereotype.Component;
      ​
      @Component
      public class CalculatorImpl implements Calculator{
        @Override
          public int add(int i, int j) {
              int result = i + j;
              System.out.println("方法内部 result = " + result);
              return result;
          }
          @Override
          public int sub(int i, int j) {
              int result = i - j;
              System.out.println("方法内部 result = " + result);
              return result;
          }
          @Override
          public int mul(int i, int j) {
              int result = i * j;
              System.out.println("方法内部 result = " + result);
              return result;
          }
          @Override
          public int div(int i, int j) {
              int result = i / j;
              System.out.println("方法内部 result = " + result);
              return result;
          }
      }
      ​
  4. 切面编码

    //@Aspect表示这个类是一个切面类
    @Aspect
    //@Component注解保证这个切面类能够放入IOC容器  也可以换成@controller等
    @Component
    public class LoggerAspect {
      
      //切点  要添加改切面功能的类的路径
      @Pointcut("execution(* com.AOP.test.CalculatorImpl.*(..))")
      public void poincut() {
        
      }
      
      //前置通知
      @Before("poincut()")
      public void beforeMethod(JoinPoint joinPoint){
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
        }
      //后置通知
      @After("poincut()")
       public  void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String args = Arrays.toString(joinPoint.getArgs());
        System.out.println("Logger-->后置通知,方法名:"+methodName+",参数:"+args);
      }
      //返回通知
      @AfterReturning(value = "poincut()",returning = "result")
      public void afterReturningMethod(JoinPoint joinPoint, Object result){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);
      } 
      //异常通知
      @AfterThrowing(value = "poincut()", throwing = "ex")
      public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
        String methodName = joinPoint.getSignature().getName();
        System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
      }
      
        //环绕通知
        @Around("poincut()")
        public Object aroundMethod(ProceedingJoinPoint joinPoint){
               String methodName = joinPoint.getSignature().getName();
          String args = Arrays.toString(joinPoint.getArgs());
          Object result = null;
               try {
                   System.out.println("环绕通知-->目标对象方法执行之前");
                   //目标对象(连接点)方法的执行
                   result = joinPoint.proceed();
                   System.out.println("环绕通知-->目标对象方法返回值之后");
          } catch (Throwable throwable) {
                   throwable.printStackTrace();
                   System.out.println("环绕通知-->目标对象方法出现异常时");
          } finally {
            System.out.println("环绕通知-->目标对象方法执行完毕");
          }
          return result;
        }
      
    ​
  5. 测试

      public static void main(String[] args) {
        // TODO Auto-generated method stub
        ApplicationContext ac= new ClassPathXmlApplicationContext("applicationContext3.xml");
        Calculator calculator = ac.getBean(Calculator.class);
        calculator.div(1, 3);//连接点
      }
  6. 结果

    环绕通知-->目标对象方法执行之前
    Logger-->前置通知,方法名:div,参数:[1, 3]
    方法内部 result = 0
    环绕通知-->目标对象方法返回值之后
    环绕通知-->目标对象方法执行完毕
    Logger-->后置通知,方法名:div,参数:[1, 3]
    Logger-->返回通知,方法名:div,结果:0

3.4 基于XML的AOP

3.4.1 切面
public class CheckLogin {
​
  public void beforeUser(JoinPoint joinPoint) {
    //String args = Arrays.toString(joinPoint.getArgs());
    String methodName = joinPoint.getSignature().getName();
    String args = Arrays.toString(joinPoint.getArgs());
    System.out.println("方法名:"+methodName+",参数:"+args);
    //将得到的参数与数据库中的匹配,如果一致,允许登陆
    System.out.println("检查登陆权限");
  }
  
  public void afterUser() {
    System.out.println("提交事务");
  }
}
3.4.2 切点
  @Override
  public boolean login(String userName, String passwd) {
    //System.out.println("user..userName="+user.getUserName()+","+"pwssw="+user.getPasswd());
     return true;
  }
3.4.3 配置
<!-- 切面 -->
<bean id="userAspect" class="com.springtest.service.CheckLogin"></bean>
   <!-- 切点 -->
   <aop:config>
        <aop:aspect ref="userAspect">
              <aop:pointcut expression="execution(* *.*User(..))" id="userPointCut"/>
                   <aop:before method="beforeUser" pointcut-ref="userPointCut"/>
                   <aop:after  method="afterUser" pointcut-ref="userPointCut"/>
                        
        </aop:aspect>
    </aop:config>
</beans>

4、声明式事务

4.1 数据库事务的四大特性

一、原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功 要么全部失败回滚 因此 事务的操作如果成功就必须要完全应用到数据库 如果操作失败则不能对数据库有任何影响

二、一致性(Consistency) 一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态 也就是说一个事务执行之前和执行之后都必须处于一致性状态

例:转账时 用户A和用户B两者的钱加起来一共是5000 那么不管A和B之间如何转账 转几次账 事务结束后两个用户的钱相加起来应该还得是5000

三、隔离性(Isolation) 隔离性是当多个用户并发访问数据库时 比如操作同一张表时 数据库为每一个用户开启的事务不能被其他事务的操作所干扰 多个并发事务之间要相互隔离

即 对于任意两个并发的事务T1和T2 在事务T1看来 T2要么在T1开始之前就已经结束 要么在T1结束之后才开始 每个事务都感觉不到有其他事务在并发地执行

四、持久性(Durability) 持久性是指一个事务一旦被提交了 那么对数据库中的数据的改变是永久性的 即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作

例如:在使用JDBC操作数据库时 在提交事务方法后 提示用户事务操作完成 当程序执行完成直到看到提示后就可以认定事务以及正确提交 即使这时候数据库出现了问题也必须要将的事务完全执行完成 否则就会造成看到提示事务处理完毕 但是数据库因为故障而没有执行事务的重大错误

4.2 基于XML的声明式事务

在application.xml里配置。保持事务的四大特性

<!-- 配置事务管理 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据源  保证了数据库的四大特性-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 通知 -->   
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 每次调用这些方法都会启动 事务管理  每个在config里配置的方法都要以以下方式开头-->
            <tx:method name="save*" propagation="REQUIRED" />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
    <!-- 切点 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.buyerApp.*.*.*.service.*(..))"/>
    </aop:config> <!--每次调用service层的数据库语句都会收到上面各个方法的通知,若代码错误 数据库就会回滚  -->
    

5、mybatis+spring

  1. 配置 applicationContext.xml 数据库使用c3p0 sqlSessionFactory被集成为一个bean

    <?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:tx="http://www.springframework.org/schema/tx" 
      xmlns:aop="http://www.springframework.org/schema/aop" 
      xmlns:context="http://www.springframework.org/schema/context" 
      xsi:schemaLocation="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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">
      
        <!-- 引入数据库配置文件 -->
        <context:property-placeholder location="classpath:jdbc.properties"/>
      <!-- 数据源 -->
      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
            <property name="jdbcUrl" value="${jdbc.url}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <!-- 初始连接池大小  -->
            <property name="initialPoolSize" value="2"/>
            <!-- 连接池中连接最小个数 -->
            <property name="minPoolSize" value="1"/>
            <property name="maxPoolSize" value="5"/>
      </bean> 
      
      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"></property>
      </bean>
      <!--扫描mapper 并称为bean-->
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.mapper"></property>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>    
      </bean>
      
      <!-- 配置事务管理 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源  保证了数据库的四大特性-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!-- 通知 -->   
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- 传播行为 每次调用这些方法都会启动 事务管理  每个在config里配置的方法都要以以下方式开头-->
                <tx:method name="save*" propagation="REQUIRED" />
                <tx:method name="insert*" propagation="REQUIRED" />
                <tx:method name="delete*" propagation="REQUIRED" />
                <tx:method name="update*" propagation="REQUIRED" />
                <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
                <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
            </tx:attributes>
        </tx:advice>
        <!-- 切点 -->
        <aop:config>
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.buyerApp.*.*.*.service.*(..))"/>
        </aop:config> <!--每次调用service层的数据库语句都会收到上面各个方法的通知,若代码错误 数据库就会回滚  -->
        
       <bean id="user" class="com.pojo.User"></bean>
    </beans>
  2. 配置数据库连接信息 jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/buyerapp?characterEncoding=utf-8
    jdbc.username=root
    jdbc.password=123456
    ​
    jdbc.initialPoolSize=5
    jdbc.minPoolSize=1
    jdbc.maxPoolSize=10
  3. 配置逆向工程文件 generator.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>
     <context id="DB2Tables" targetRuntime="MyBatis3">
     
     
      <commentGenerator>
       <!--
            suppressAllComments属性值:
              true:自动生成实体类、SQL映射文件时没有注释
              false:自动生成实体类、SQL映射文件,并附有注释
            -->
       <property name="suppressAllComments" value="true" />
      </commentGenerator>
      <!-- 数据库连接信息 -->
      <jdbcConnection driverClass="com.mysql.jdbc.Driver"
       connectionURL="jdbc:mysql://localhost:3306/buyerapp?characterEncoding=UTF-8" 
       userId="root"  password="123456">
      </jdbcConnection>
      <!-- 
          forceBigDecimals属性值: 
            true:把数据表中的DECIMAL和NUMERIC类型,
    解析为JAVA代码中的java.math.BigDecimal类型 
            false(默认):把数据表中的DECIMAL和NUMERIC类型,
    解析为解析为JAVA代码中的Integer类型 
        -->
      <javaTypeResolver>
       <property name="forceBigDecimals" value="false" />
      </javaTypeResolver>
       
        <!-- 
          targetProject属性值:实体类pojo的生成位置  
          targetPackage属性值:实体类所在包的路径
        --> 
      <javaModelGenerator targetPackage="com.pojo"
                                 targetProject="Spring-Mybatis/src">
       <!-- trimStrings属性值:
            true:对数据库的查询结果进行trim操作
            false(默认):不进行trim操作
            -->
       <property name="trimStrings" value="true" />
      </javaModelGenerator>
       <!-- 
          targetProject属性值:SQL映射文件的生成位置(写数据库语句类)  
          targetPackage属性值:SQL映射文件所在包的路径(xml)
        -->
       
      <sqlMapGenerator targetPackage="com.mapper" targetProject="Spring-Mybatis/src">
      </sqlMapGenerator>
       
      <!-- 生成动态代理的接口  -->
      <javaClientGenerator type="XMLMAPPER" targetPackage="com.mapper" targetProject="Spring-Mybatis/src">
      </javaClientGenerator>
     
      <!-- 指定数据库表  -->
      <table tableName="admin">
      <property name="useActualColumnNames" value="false"/>
       </table>
          
     </context>
    </generatorConfiguration>
  4. 测试

      public static void main(String[] args) {
        // TODO Auto-generated method stub
              ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
              UserMapper userMapper =  (UserMapper) ac.getBean("userMapper");
              User user = (User)ac.getBean("user");
              user.setUserPhone("123");
              user.setUserPwd("321");
              int result = userMapper.insert(user);
              if (result>0) {
          System.out.println("添加成功");
        }else {
          System.out.println("添加失败");
        }
      }

三:SpringMVC

1、SpringMVC简介

1.1、什么是MVC

MVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分

M:Model,模型层,指工程中的JavaBean,作用是处理数据

JavaBean分为两类:

  • 一类称为实体类Bean:专门存储业务数据的,如 Student、User 等

  • 一类称为业务处理 Bean:指 Service 或 Dao 对象,专门用于处理业务逻辑和数据访问。

V:View,视图层,指工程中的html或jsp等页面,作用是与用户进行交互,展示数据

C:Controller,控制层,指工程中的servlet,作用是接收请求和响应浏览器

MVC的工作流程: 用户通过视图层发送请求到服务器,在服务器中请求被Controller接收,Controller

调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller再根据请求处理的结果

找到相应的View视图,渲染数据后最终响应给浏览器

1.2、什么是SpringMVC

SpringMVC是Spring的一个后续产品,是Spring的一个子项目

SpringMVC 是 Spring 为表述层开发提供的一整套完备的解决方案。在表述层框架历经 Strust、

WebWork、Strust2 等诸多产品的历代更迭之后,目前业界普遍选择了 SpringMVC 作为 Java EE 项目

表述层开发的首选方案

注:三层架构分为表述层(或表示层)、业务逻辑层、数据访问层,表述层表示前台页面和后台

servlet

1.3、SpringMVC的特点

  • Spring 家族原生产品,与 IOC 容器等基础设施无缝对接

  • 基于原生的Servlet,通过了功能强大的前端控制器DispatcherServlet,对请求和响应进行统一

处理

  • 表述层各细分领域需要解决的问题全方位覆盖,提供全面解决方案

  • 代码清新简洁,大幅度提升开发效率

  • 内部组件化程度高,可插拔式组件即插即用,想要什么功能配置相应组件即可

  • 性能卓著,尤其适合现代大型、超大型互联网项目要求

2、入门案例

2.1 开发环境

tomcat 9.0 或者8.x

jdk 1.8

2.2 创建项目

  1. 创建动态web项目

  2. 导入jar 包 spring-web-4.3.25.RELEASE.jar spring-webmvc-4.3.25.RELEASE.jar

2.3 配置web.xml

<!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 --> 
 <servlet>
   <!-- servlet-name名字随便起,但两个servlet-name 必须一致-->
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
       <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
      <param-name>contextConfigLocation</param-name>
      <param-value>classPath:spring-mvc.xml</param-value>
    </init-param>
  <!--
        作为框架的核心组件,在启动过程中有大量的初始化操作要做
        而这些操作放在第一次请求时才执行会严重影响访问速度
        因此需要通过此标签将启动控制DispatcherServlet的初始化时间提前到服务器启动时
  -->
    <load-on-startup>1</load-on-startup>
  </servlet>
  
<!--统一路径 以xxxx.action结尾-->
  <servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>*.action</url-pattern>
  </servlet-mapping>

2.4 配置 spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
  xmlns:mvc="http://www.springframework.org/schema/mvc" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:tx="http://www.springframework.org/schema/tx" 
  xmlns:aop="http://www.springframework.org/schema/aop" 
  xmlns:context="http://www.springframework.org/schema/context" 
  xsi:schemaLocation="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.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd 
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop.xsd ">
      
  <!-- 全局扫描处理器  将com.mvc.controller加入bean-->
  <context:component-scan base-package="com.mvc.controller"></context:component-scan>
      
    <!--处理器映射器 和处理器适配器-->
  <mvc:annotation-driven></mvc:annotation-driven>
  
    <!-- 视图解析器  解析视图  位置在/WEB-INF底下  以.jsp结尾的文件-->
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF"></property>
    <property name="suffix" value=".jsp"></property>
  </bean>
​
  
</beans>

2.5 创建login.jsp

   <form action="${pageContext.request.contextPath}/user/login.action" method="post">
       用户名 <input type="text" name="username" id="username">
       密码 <input type="text" name="paswd" id="paswd">
       <input type="submit" value="登陆">
   </form>

action="${pageContext.request.contextPath}/具体某个类上的路径/该类方法上的路径.action action是在XML上配置的统一路径映射规律

2.6编写controller

package com.mvc.controller;
​
import java.util.HashMap;
import java.util.Map;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
//@Controller 加入IOC容器管理 成为一个bean
@Controller
@RequestMapping("/user")
public class UserController {
  
  @RequestMapping("/login")
  public ModelAndView login(HttpServletRequest request,HttpServletResponse response) throws Exception {
    ModelAndView result = new ModelAndView();
    String username = request.getParameter("username");
    String paswd = request.getParameter("paswd");
    
    Map<String, Object> map = new HashMap<>();
    map.put("username", username);
    map.put("paswd", paswd);
    result.setViewName("/list");   //WEB-INF/list.jsp
    result.addObject("map",map);
    return result;
  }
  
}

@RequestMapping标识一个类:设置映射请求的请求路径的初始信息

@RequestMapping标识一个方法:设置映射请求请求路径的具体信息

3、 JSON

3.1注册json转换器

4、解决乱码问题

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
 
   <!-- 乱码过滤器一定要设置 -->
   <filter>
      <filter-name>characterEncodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
      </init-param>
    </filter>
    <!-- 过滤 -->
  <filter-mapping>
      <filter-name>characterEncodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <!-- 配置SpringMVC的前端控制器,对浏览器发送的请求统一进行处理 --> 
  <servlet>
   <!-- servlet-name名字随便起,但两个servlet-name 必须一致-->
    <servlet-name>mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
       <!-- 通过初始化参数指定SpringMVC配置文件的位置和名称 -->
      <param-name>contextConfigLocation</param-name>
      <param-value>classPath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
<!--统一路径 以xxxx.action结尾-->
  <servlet-mapping>
    <servlet-name>mvc</servlet-name>
    <url-pattern>*.action</url-pattern>
  </servlet-mapping>
  
  <display-name>SpringMvcTest-2024</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.jsp</welcome-file>
    <welcome-file>default.htm</welcome-file>
  </welcome-file-list>
</web-app>

5、 拦截器

  <!-- 拦截器 -->
  <mvc:interceptors>
   <!--登录拦截器-->
      <mvc:interceptor>
         <mvc:mapping path="/**"/>   <!--全路径拦截  用户没登录不能访问后台  -->
         <!--除了用户进行登入 -->
          <mvc:exclude-mapping path="/api/userInfo/userLogin*"/>
          <bean class="com.interceptor.LoginInterceptor"/>
      </mvc:interceptor>
  </mvc:interceptors> 
package com.buyerApp.interceptor;
​
import java.util.HashMap;
import java.util.Map;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
​
import com.alibaba.fastjson.JSON;
​
​
public class LoginInterceptor implements HandlerInterceptor {
​
  @Override
  public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)
      throws Exception {
    
​
  }
​
  @Override
  public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)
      throws Exception {
​
​
  }
​
  //springmvc的拦截 ,每次执行controller里的方法时都会先执行下面的方法  获取token 判断用户有无登录
  @Override
  public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object arg2) throws Exception {
    String  token=req.getHeader("token");
    String user_Phone = req.getHeader("user_Phone");//用户
    
    String shop_token = req.getHeader("shop_token");
    String shop_shh = req.getHeader("shop_shh");//商户
    
    String deliver_token = req.getHeader("deliver_token");
    String deliver_Phone = req.getHeader("deliver_Phone");//骑手
    
    String admin_token = req.getHeader("admin_token");
    String admin_Phone = req.getHeader("admin_Phone");//管理员
    
    Map<String, Object> resultMap=new HashMap<String, Object>();    
    if( !StringUtils.isEmpty(token) && !StringUtils.isEmpty(user_Phone) && !"undefined".equalsIgnoreCase(token) && !"undefined".equalsIgnoreCase(user_Phone)) {
      return true;
    }else {
      System.out.println("拦截。。。。。");
      req.setCharacterEncoding("UTF-8");
      resp.setContentType("text/html;charset=UTF-8");
      resultMap.put("success", 0);
      resultMap.put("msg", "您还未登录,请先登录!");   
      String result=JSON.toJSONString(resultMap);
      resp.getWriter().print(result);
      return false;
    }
  }
}
​

四:SSM整合

1、案例

使用Spring+SpringMVC+Mybatis开发蛋糕系统用户登陆模块。功能包括使用session或者cookie功能记录用户信息,拦截功能,操作日志(在控制台打印出即可)。

2.配置

SSMTest2024项目

Web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
  
   <!-- 加载spring容器 -->
  <context-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:com/ssmTest/system/config/applicationContext.xml</param-value>
  </context-param>
  <!-- 配置spring监听器 -->
  <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
 
  <!-- 配置解决中文post提交乱码问题 -->
  <filter>
      <filter-name>characterEncodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
      <filter-name>characterEncodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
 
  <!-- 配置springmvc前端控制器 -->
  <servlet>
      <servlet-name>springmvc</servlet-name>
      <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
      <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:com/ssmTest/system/config/spring-mvc.xml</param-value>
      </init-param>
     <!-- tomcat启动时执行前端控制器 -->
     <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>springmvc</servlet-name>
      <url-pattern>*.action</url-pattern>
  </servlet-mapping>
  
  <display-name>SSMTest2024</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.jsp</welcome-file>
    <welcome-file>default.htm</welcome-file>
  </welcome-file-list>
</web-app>
  • 21
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陈编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值