JDK动态代理+cglib代理+mybatis优化开发

一.回顾

1.编写pom.xml文件,导入相关jar包,添加依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.oracle</groupId>
    <artifactId>mybatis07</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>



    <!--添加项目依赖-->
    <dependencies>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!--数据库驱动-->
        <!--驱动的版本最好和你的数据库版本保持一致-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--alibaba json工具,他和mybatis没关系-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>
    </dependencies>



    <!--如果AccountMapper.xml放在src/main/java下的包下,需要添加build
        如果是放在resource下,则不需要添加依赖
        一般情况下,mapper.xml最好放到resources下
        这样就不用写build了

    -->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>

            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>

        </resources>

        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
            </plugin>
        </plugins>
    </build>


</project>

2.编写核心配置文件

mybatis-config.xml

建议是放在resource下的包中,

注意,在resource下建包要一级一级的去建,不能像在Java目录下建包一样;

如:

​ 建一个com.oracle.mapper包,要先建com,再建oracle,再键mapper,

如果直接com.oracle.mapper的方式来建包,只是一个包

<?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 命名空间,程序中通过 sql映射的唯一标识获取 sql,比如当前文件有一个 insert sql标识,但是
其他文件中也有一个insert sql标识,就无法区分了,所以不同的sql功能,操作,使用namespce进行区分-->
<!--建议namaspce的值 为 包名.文件名-->
<mapper namespace="com.oracle.mapper.AccountMapper">
    <!--查询  -->
    <!--sql代码-->
    <!--mybatis中提供了四个最基础的sql标签,增删改查-->
    <!--id是程序中获取sql的标识,完整的标识是 namespace.sqlID-->
    <!--resultType 结果返回类型-->
    <select id="selectAll" resultType="account">

        select aid,aname,apassword,a_nickname as anickname from account
    </select>

    <!--通过id查询,#{} 占位符,程序会自动根据参数的类型,选择是否增加 '' -->
    <select id="selectById" resultType="account">
        select aid, aname, apassword, a_nickname as anickname
        from account
        where aid = ${value}
    </select>

    <select id="selectByName" resultType="account" >
        select aid, aname, apassword, a_nickname as anickname
        from account
        where aname like #{aname}
    </select>

    <select id="selectByName2" resultType="account">
    select aid, aname, apassword, a_nickname  as  anickname
    from account
    where aname like '%${value}%'
</select>


    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--useGeneratedKeys 使用插入后的主键值-->
    <!--keyProperty将主键值映射到哪一属性上-->
    <!--主键自动获取前提是 数据库的主键生成方式 为 自动增长 mysql,sqlserver。 oracle不是的-->
   <insert id="insert" useGeneratedKeys="true" keyProperty="aid">
       insert
       into
       account(aname,apassword,a_nickname)
       values(#{aname},#{apassword},#{anickname})
   </insert>

    <update id="update">
        update account
        set aname = #{aname}, apassword = #{apassword}, a_nickname = #{anickname}
        where aid = #{aid}
    </update>

    <delete id="delete">
        delete from account
        where aid = #{aid}
    </delete>





</mapper>

3.在resources下编写com.oracle.mapper下编写AccountMapper.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">


<!--mapper就是映射的意思-->

<!--namespace 命名空间,程序中通过 sql映射的唯一标识获取 sql,比如当前文件有一个 insert sql标识,但是
其他文件中也有一个insert sql标识,就无法区分了,所以不同的sql功能,操作,使用namespce进行区分-->
<!--建议namaspce的值 为 包名.文件名-->
<mapper namespace="com.oracle.mapper.AccountMapper">
    <!--查询  -->
    <!--sql代码-->
    <!--mybatis中提供了四个最基础的sql标签,增删改查-->
    <!--id是程序中获取sql的标识,完整的标识是 namespace.sqlID-->
    <!--resultType 结果返回类型-->
    <select id="selectAll" resultType="account">

        select aid,aname,apassword,a_nickname as anickname from account
    </select>

    <!--通过id查询,#{} 占位符,程序会自动根据参数的类型,选择是否增加 '' -->
    <select id="selectById" resultType="account">
        select aid, aname, apassword, a_nickname as anickname
        from account
        where aid = ${value}
    </select>

    <select id="selectByName" resultType="account" >
        select aid, aname, apassword, a_nickname as anickname
        from account
        where aname like #{aname}
    </select>

    <select id="selectByName2" resultType="account">
    select aid, aname, apassword, a_nickname  as  anickname
    from account
    where aname like '%${value}%'
</select>


    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--useGeneratedKeys 使用插入后的主键值-->
    <!--keyProperty将主键值映射到哪一属性上-->
    <!--主键自动获取前提是 数据库的主键生成方式 为 自动增长 mysql,sqlserver。 oracle不是的-->
   <insert id="insert" useGeneratedKeys="true" keyProperty="aid">
       insert
       into
       account(aname,apassword,a_nickname)
       values(#{aname},#{apassword},#{anickname})
   </insert>

    <update id="update">
        update account
        set aname = #{aname}, apassword = #{apassword}, a_nickname = #{anickname}
        where aid = #{aid}
    </update>

    <delete id="delete">
        delete from account
        where aid = #{aid}
    </delete>



</mapper>

剩下就是测试,本篇博客将对以前的方式进行优化开发

二.#{}和${}的区别

#{}的实质是去找model中的get和set方法,要和get中的属性值保持一致

${}只是简单的替换

经常碰到这样的面试题目:#{}和${}的区别是什么?
 
网上的答案是:#{}是预编译处理,$ {}是字符串替换。mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值;mybatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。使用 #{} 可以有效的防止SQL注入,提高系统安全性。
 
对于这个题目我感觉要抓住两点:
(1)$ 符号一般用来当作占位符,常使用Linux脚本的人应该对此有更深的体会吧。既然是占位符,当然就是被用来替换的。知道了这点就能很容易区分$和#,从而不容易记错了。
(2)预编译的机制。预编译是提前对SQL语句进行预编译,而其后注入的参数将不会再进行SQL编译。我们知道,SQL注入是发生在编译的过程中,因为恶意注入了某些特殊字符,最后被编译成了恶意的执行操作。而预编译机制则可以很好的防止SQL注入。
 
最后想说的是,对于mybatis 以及 sql 而言,每一个考点背后都是有一个深刻的思想存在的,应该好好的体会。这样才能真正的做到技术提升,成为技术大牛。

#{}和${}都表示参数的替换,不同之处在于:

  • #{},占位符,会根据参数的类型,自动选择是否增加单引号,本质就是利用了PreparedStatement;
  • ${}只是替换,也就是说,你传递String字符串,也是直接替换,不会为你自动增加单引号;

1.#{}

(1)当参数类型是简单类型时,(Java8种基本数据类型),#{}中的值可以随便编写,如:#{aid},#{1}

(2)如果参数类型是自定义的Java类型,那么只可以编写类的属性,而且要区分大小写。

#{}使用OGNL表达式获取对象中的数据

<insert id="insert">
    insert into account(aname, apassword, a_nickname)
    values
    (#{aname}, #{apassword}, #{anickname})
</insert>

public void insert(Account account) {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        sqlSession.insert("org.westos.mapper.AccountMapper.insert", account);
        sqlSession.commit();
    }
}

以上两个代码的结合,就是传递了Account对象,但是在SQL运行的时候,获取到了对象的属性值,这种从对象中获取属性值的方式,是通过OGNL对象图导航表达式语言完成的。

总结:#{}如果参数是非自定义对象,值可以随意填写;如果参数是自定义对象,那么值必须为属性。

#{}可以根据数据类型自动选择是否增加单引号。

2.${}

在mybatis-config.xml中,我们使用${}读取.properties配置文件中的值,还可以用在mapper.xml中。注意版本为3.4.6显示出来错误

当参数是Integer时,#{aid}我们发现是能够获取值的。

<select id="queryOne" resultType="account">
    select aid, aname, apassword, a_nickname anickname
    from account
    where aid = #{aid}
</select>

但是,${aid}会报错,

<select id="queryOne" resultType="account">
    select aid, aname, apassword, a_nickname anickname
    from account
    where aid = ${aid}
</select>

public Account queryOne(Integer aid) {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        return sqlSession.selectOne("org.westos.mapper.AccountMapper.queryOne", aid);
    }
}

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'aid' in 'class java.lang.Integer'
### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'aid' in 'class java.lang.Integer'

把Integer当做了自定义对象,寻找属性aid了,然后就会报找不到错误。

a i d 改 为 {aid}改为 aid{value}。

<select id="queryOne" resultType="account">
    select aid, aname, apassword, a_nickname anickname
    from account
    where aid = ${value}
</select>

成功。

与之类似的还有String类型参数。

如果参数是String类型,${aname}的情况下,把String当做了自定义对象,寻找aname属性;

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'aname' in 'class java.lang.String'
### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'aname' in 'class java.lang.String'

那么此时需要改为${value};

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'lisi' in 'where clause'
### The error may exist in org/westos/mapper/AccountMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select aid, aname, apassword, a_nickname anickname         from account         where aname = lisi
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'lisi' in 'where clause'

在改为 v a l u e 后 , 由 于 {value}后,由于 value{}不会为字符串类型自动添加单引号,所以还是有缺陷。

如果参数是对象类型,${对象的属性},还是通过OGNL运算给数值。

但是,${对象的属性}只是替换不会因为你是字符串就自动给你加上单引号。

ps:所以在传递对象作为参数时,选用#{}是最好的选择。

<insert id="insert">
    insert into account(aname, apassword, a_nickname)
    values
    (${aname}, ${apassword}, ${anickname})
</insert>

org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'wangwu' in 'field list'
### The error may exist in org/westos/mapper/AccountMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: insert into account(aname, apassword, a_nickname)         values         (wangwu, uwgnaw, 王五)
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'wangwu' in 'field list'

3.模糊查询

使用#{}

<!--模糊查询-->
<select id="selectByName1" resultType="account">
    select aid, aname, apassword, a_nickname anickname
    from account
    where aname like #{aname}
</select>

AccountMapper.java

//模糊查询
public List<Account> selectByName(String name) {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        return sqlSession.selectList("org.westos.mapper.AccountMapper.selectByName1", name);
    }
}

测试方法

@Test
public void selectByName() {
    List<Account> list = accountMapper.selectByName("%zhang%");
    System.out.println(list);
}

如果使用#{},那么在参数中必须把%设置好了。

[Account(aid=1, aname=zhangsan, apassword=nasgnahz, anickname=张三)]

使用${}

<select id="selectByName2" resultType="account">
    select aid, aname, apassword, a_nickname anickname
    from account
    where aname like '%${value}%'
</select>

AccountMapper.java

//模糊查询
public List<Account> selectByName(String name) {
    try (SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSessionFactory().openSession()) {
        return sqlSession.selectList("org.westos.mapper.AccountMapper.selectByName2", name);
    }
}

测试方法:

@Test
public void selectByName() {
    List<Account> list = accountMapper.selectByName("zhang");
    System.out.println(list);
}

[Account(aid=1, aname=zhangsan, apassword=nasgnahz, anickname=张三)]

但是,${}存在SQL注入的风险,慎用。

@Test
public void selectByName() {
    List<Account> list = accountMapper.selectByName("a' or 1 = 1 -- ");
    System.out.println(list);
}

最终的SQL为:

SELECT * FROM account WHERE aname LIKE '%a' OR 1 = 1 -- %';

1后面的--为SQL语言的注释。

查询结果为数据库中的所有数据:

[Account(aid=1, aname=zhangsan, apassword=nasgnahz, anickname=张三), Account(aid=2, aname=lisi, apassword=isil, anickname=李四)]

总结:${}如果参数是非自定义对象,那么值只能为value;如果参数是自定义对象,那么值必须为属性;

${}存在Sql注入的风险;

${}不会根据数据类型自动增加单引号。

4.总结

查询的参数是基本类型(四类八种),必须使用#{},如#{1},#{id}

查询的参数是integer类型,#{id}不报错,${id}报错,原因是将integer当成了自定义对象,去寻找属性id	了,然后就会报找不到错误。需要将${id}换为${value}


查询的参数如果是自定义对象,那么值必须为属性,如#{aname};
	如果参数是String类型,${aname}的情况下,把String当做了自定义对象,寻找aname属性;同样			报找不到错误;那么此时需要改为${value};在改为${value}后,由于${}不会为字符串类型自				动添加单引号,所以还是有缺陷。
	
	
如果参数是对象类型,${对象的属性},还是通过OGNL运算给数值。

但是,${对象的属性}只是替换,不会因为你是字符串就自动给你加上单引号。

ps:所以在传递对象作为参数时,选用#{}是最好的选择。

#{}

	使用#{}意味着使用的预编译的语句,即在使用jdbc时的preparedStatement,sql语句中如果存在参数则会使用?作占位符,我们知道这种方式可以防止sql注入,并且在使用#{}时形成的sql语句,已经带有引号,例,select  * from table1 where id=#{id}  在调用这个语句时我们可以通过后台看到打印出的sql为:select * from table1 where id='2' 加入传的值为2.也就是说在组成sql语句的时候把参数默认为字符串。

${}

	使用${}时的sql不会当做字符串处理,是什么就是什么,如上边的语句:select * from table1 where id=${id} 在调用这个语句时控制台打印的为:select * from table1 where id=2 ,假设传的参数值为2

从上边的介绍可以看出这两种方式的区别,我们最好是能用#{}则用它,因为它可以防止sql注入,且是预编译的,在需要原样输出时才使用${},如,

select * from ${tableName} order by ${id} 这里需要传入表名和按照哪个列进行排序 ,加入传入table1、id 则语句为:select * from table1 order by id

如果是使用#{} 则变成了select * from 'table1' order by 'id' 我们知道这样就不对了。

另,在使用以下的配置时,必须使用#{}

<select id="selectMessageByIdI" parameterType="int" resultType="Message">
         
         select * from message where id=#{id};
     </select>

在parameterType是int时,sql语句中必须是#{}。

三.MyBatis优化开发

1.日志

当我们程序发生错误的时候,我们会在控制台看我们报的错误,但是我们不止想要知道我们的错误的信息,还想知道我们在程序运行过程中的其他信息,这就要使用我们的日志技术,在实际的开发中,日志是十分重要的,当项目越来越大的时候,有时候一个小的错误或者逻辑bug我们没有发现,导致了后面的开发出现问题,我们就需要查阅之前的日志进行分析,最终找到我们的问题所在。

简而言之,日志其实就是:MyBatis在执行过程中的信息显示,尤其是SQL参数。


程序编写完成之后,目前我们是打包为 war包,war包最后运行在服务器上得tomcat程序中,
    假设,现在有客户反馈说,注册功能特别得慢?
    查看注册功能得具体日志信息。
    日志对于程序得调优,问题得排查,监测 都具有非常大得意义。
    最普通得日志,就是 System.out.println();
    sysout 输出非常不好,
    io操作会影响程序运行得速度,如果我们又不需要某段日志,必须手动删除/注释
    sysout只是输出在控制台中,能不能发个邮件,能不能存在 一个 文件中
    日志处理解决方案
    apache LOG4J 不局限于log4j
    几乎所有得框架都有日志得记录,通过这个框架日志,就可以查看框架执行得过程,细节。
    我们目前想看得mybatis得 执行sql和参数

1.LOG4J的使用

使用步骤:
    maven 依赖
    核心配置文件 默认是在 classes:log4j.properties
    取得日志记录器
    private Logger logger =Logger.getLogger(DBHelper.class);
    记录日志
    注意级别

1.在pom.xml里加入依赖

 <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

2.导入两个核心配置文件

需要再写一个配置文件:名字固定:log4j.properties.放在resources下。

内容:

log4j.properties

#USE THIS SETTING FOR OUTPUT MYBATIS`s SQL ON THE CONSOLE
log4j.rootLogger=error, Console

#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
#log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.appender.Console.layout.ConversionPattern=[ %l ] %d{yyyy/MM/dd HH:mm:ss} %-5p [%c{1}] - %l - %m%n


log4j2.properties

log4j.rootLogger=debug, Console ,File  ,RollingFile,errorFile,excuteLogFile

#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender  
log4j.appender.Console.layout=org.apache.log4j.PatternLayout  
log4j.appender.Console.layout.ConversionPattern=%p,%d, %m%n
log4j.appender.Console.Threshold=DEBUG
log4j.appender.Console.filter.F12=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.Console.filter.F12.LevelMin=DEBUG
log4j.appender.Console.filter.F12.LevelMax=DEBUG
#File
#\u5546\u6807\u65F6\u95F4\u5DF2\u8FC7\u671F
log4j.appender.File = org.apache.log4j.RollingFileAppender
log4j.appender.File.File = \u65E5\u5FD7/\u5DF2\u8FC7\u671F\u6570\u636E/\u5DF2\u8FC7\u671F\u672A\u8D85\u534A\u5E74.csv
log4j.appender.File.MaxFileSize=10MB
log4j.appender.File.MaxBackupIndex=1000
log4j.appender.File.Threshold=INFO
log4j.appender.File.layout = org.apache.log4j.PatternLayout
log4j.appender.File.layout.ConversionPattern =[%p],%m%n
#log4j.appender.debugfile.encoding=UTF-8
#\u7ED9appender\u6DFB\u52A0\u62E6\u622A\u5668\uFF0C\u8BBE\u5B9A\u6700\u5927\u7EA7\u522B\u8DDF\u6700\u5C0F\u7EA7\u522B\uFF0C\u62E6\u622A\u6389\u5176\u4ED6\u7684\u7EA7\u522B\u7684\u65E5\u5FD7\u4FE1\u606F\u3002
log4j.appender.File.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.File.filter.F1.LevelMin=INFO
log4j.appender.File.filter.F1.LevelMax=INFO
#\u7248\u6743\u58F0\u660E\uFF1A\u672C\u6587\u4E3ACSDN\u535A\u4E3B\u300Cweixin_42760466\u300D\u7684\u539F\u521B\u6587\u7AE0\uFF0C\u9075\u5FAACC 4.0 BY-SA\u7248\u6743\u534F\u8BAE\uFF0C\u8F6C\u8F7D\u8BF7\u9644\u4E0A\u539F\u6587\u51FA\u5904\u94FE\u63A5\u53CA\u672C\u58F0\u660E\u3002
#\u539F\u6587\u94FE\u63A5\uFF1Ahttps://blog.csdn.net/weixin_42760466/java/article/details/87278237

#RollingFile#
#\u5546\u6807\u65F6\u95F4\u672A\u8FC7\u671F
log4j.appender.RollingFile = org.apache.log4j.RollingFileAppender
log4j.appender.RollingFile.File = \u65E5\u5FD7/\u672A\u8FC7\u671F\u6570\u636E/\u4E00\u5E74\u5185\u5C06\u8FC7\u671F.csv
log4j.appender.RollingFile.MaxFileSize=10MB
log4j.appender.RollingFile.MaxBackupIndex=1000
log4j.appender.RollingFile.Threshold=WARN
log4j.appender.RollingFile.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.RollingFile.filter.F1.LevelMin=WARN
log4j.appender.RollingFile.filter.F1.LevelMax=WARN
#log4j.appender.RollingFile.MaxBackupIndex=3
log4j.appender.RollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.RollingFile.layout.ConversionPattern =[%p],%m%n

#RollingFile
log4j.appender.errorFile = org.apache.log4j.RollingFileAppender
log4j.appender.errorFile.File = \u65E5\u5FD7/\u65E0\u6548\u6570\u636E/\u65E0\u6548\u6570\u636E\u539F\u6570\u636E\u9519\u8BEF.csv
log4j.appender.errorFile.MaxFileSize=10MB
log4j.appender.errorFile.MaxBackupIndex=1000
log4j.appender.errorFile.Threshold=ERROR
log4j.appender.errorFile.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.errorFile.filter.F1.LevelMin=ERROR
log4j.appender.errorFile.filter.F1.LevelMax=ERROR
#log4j.appender.RollingFile.MaxBackupIndex=3
log4j.appender.errorFile.layout = org.apache.log4j.PatternLayout
log4j.appender.errorFile.layout.ConversionPattern =[%p],%m%n

#\u6BCF\u6B21\u6267\u884C\u7684 \u6267\u884C\u65E5\u5FD7\uFF0C\u8BB0\u5F55\u6BCF\u4E2A\u6587\u4EF6\u7684 \u603B\u6761\u6570\uFF0C\u9519\u8BEF\u6761\u6570\uFF0C\u6709\u7528\u6761\u6570\u4EE5\u53CA\u6587\u4EF6\u540D\u79F0\uFF0C\u6267\u884C\u65F6\u95F4
log4j.appender.excuteLogFile = org.apache.log4j.RollingFileAppender
log4j.appender.excuteLogFile.File = \u65E5\u5FD7/\u6267\u884C\u65E5\u5FD7/\u6267\u884C\u65E5\u5FD7.csv
log4j.appender.excuteLogFile.MaxFileSize=10MB
log4j.appender.excuteLogFile.MaxBackupIndex=1000
log4j.appender.excuteLogFile.Threshold=FATAL
log4j.appender.excuteLogFile.filter.F1=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.excuteLogFile.filter.F1.LevelMin=FATAL
log4j.appender.excuteLogFile.filter.F1.LevelMax=FATAL
log4j.appender.excuteLogFile.layout = org.apache.log4j.PatternLayout
log4j.appender.excuteLogFile.layout.ConversionPattern =%d{yy/MM/dd HH:mm:ss:SSS},[%p],%m%n

在这里插入图片描述

2.日志等级

六个普通日志等级:TRACE / DEBUG / INFO / WARNING / ERROR / FATAL

还有两个特殊等级 ALL、OFF,全部日志打开和关闭,这里不做讨论

TRACE
TRACE 在线调试。
该级别日志,默认情况下,既不打印到终端也不输出到文件。此时,对程序运行效率几乎不产生影响。 n

DEBUG
DEBUG 终端查看、在线调试。
该级别日志,默认情况下会打印到终端输出,但是不会归档到日志文件。因此,一般用于开发者在程序当前启动窗口上,查看日志流水信息。

INFO
INFO 报告程序进度和状态信息。
一般这种信息都是一过性的,不会大量反复输出。
例如:连接商用库成功后,可以打印一条连库成功的信息,便于跟踪程序进展信息。

WARNING
WARNING 警告信息
程序处理中遇到非法数据或者某种可能的错误。
该错误是一过性的、可恢复的,不会影响程序继续运行,程序仍处在正常状态。

ERROR
ERROR 状态错误
该错误发生后程序仍然可以运行,但是极有可能运行在某种非正常的状态下,导致无法完成全部既定的功能。

FATAL
FATAL 致命的错误
表明程序遇到了致命的错误,必须马上终止运行。

3.测试

${value} 测试Integer类型

在这里插入图片描述

#{aname} 测试String类型,#{}会根据参数的类型自动选择是否添加单引号,而${}不会

在这里插入图片描述

四.ResultSet如何转换为Java对象

mybatis是一个框架,框架考虑得内容很多
    比如 mybatis封装了一组api接口,让我们通过接口就可以和数据库交互,屏蔽了jdbc api。
    	提供功能:参数映射,结果集映射,缓存...
    参数映射,
    	传递参数得时候,传入对象,
    	mapper sql #{属性} ${属性}
   	 	mybatis是如何通过对象得属性或者属性值得--》OGNL
   	 	mybatis要将处理后得sql语句发送给数据库执行,
    一定要使用JDBC API, 执行后得结果对象 ResultSet
    
   	 我们就要手动实现 resultset 到 java对象是如何转换得。
    编写model(entity) 很麻烦,能够一键生成,这点mybatis可以帮助我们实现。

1.数据库元数据

什么是数据库元数据?

数据库元数据,就是描述 数据的数据

如:
1.Connection
        数据库连接信息
        connection是一个连接数据
        在connection连接数据得基础上还有这个链接以外得很多数据
        比如数据库名称,数据库版本,数据库驱动...等数据,描述这些数据的对象 我们就称之为数据			库元数据
2.Resultset
    数据库结果集信息
    resultset 我们通常使用得比较多的方法是 next getXXX close
    结果集中还有一些数据,比如 有多少列,列得名称,列得类型是什么... 描述这些数据得数据就是 结
    果集元数据
    
3.有什么意义呢?
获取到了元数据之后,就可以 一键生成实体类,也可以编写工具将resultset与java对象关联。

2.结果集转换为java对象

resultset转换为java对象= resultset元数据+java反射

编写工具类进行转换

ResultSet封装为Java对象 = 结果集元数据 + 反射

package com.oracle.util;

import com.alibaba.fastjson.JSON;

import java.sql.*;

import com.oracle.model.Account;
import org.apache.log4j.Logger;
import org.junit.jupiter.api.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class DBHelper {
    //获取log4j日志记录 工具
    private Logger logger = Logger.getLogger(DBHelper.class);

    /**
     * 自定义 resultset转换java对象,了解框架实现
     *
     * @param resultSet
     * @param aClass
     * @return
     */
    
    public Object resutsetToBean(ResultSet resultSet, Class aClass) throws Exception {
        Object result = aClass.newInstance();

        //结果集元数据
        ResultSetMetaData metaData1 = resultSet.getMetaData();
        //结果集中列得个数
        int columnCount = metaData1.getColumnCount();
        //如何从resultset转换为java对象
        //数据库得列 对应 java对象得属性
        //JAVASE --> 反射
        Account account = new Account();
        //反射
        Class accountClass = Account.class;
        Method[] declaredMethods = accountClass.getDeclaredMethods();
        while (resultSet.next()) {
            for (int i = 1; i <= columnCount; i++) {
                String columnName = metaData1.getColumnName(i);
                String columnTypeName = metaData1.getColumnTypeName(i);
        // String format = String.format("列名:%s,类型:%s", columnName,

        // System.out.println(format);
        //数据结果值
                Object columnVal = resultSet.getObject(columnName);
                for (Method declaredMethod : declaredMethods) {
                    String methodName = declaredMethod.getName();
			//getAname()
                    if (methodName.toUpperCase().equals(("set" +
                            columnName).toUpperCase())) {
			// System.out.println("\t找到得得方法:" + methodName);
                        declaredMethod.invoke(result, columnVal);
                    }
                }
            }
        }
        return result;
    }

    public void save() throws Exception {
        logger.debug("开始加载驱动");
        Class.forName("com.mysql.jdbc.Driver");
        logger.debug("加载驱动成功");
        Connection connection =
                DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/db2020? serverTimezone = UTC & characterEncoding = UTF - 8", " root", " java123");
                        String sql = "select * from account where aid =1 ";
        ResultSet resultSet = connection.createStatement().executeQuery(sql);
        Object account = new DBHelper().resutsetToBean(resultSet,Account.class);

        System.out.println("自动设置值得结果:" + JSON.toJSONString(account, true));
    }
}


继续思考,有的查询是单条数据,有的是多条数据,有的是对象,有的int,还有得不是对象,但却有很
多列
大家继续封装。

在这里插入图片描述

public Object resutsetToBean(ResultSet resultSet, ResultSetHandle handle)
{

整个java操作数据库的基础API jdbc
首先要清楚jdbc五个步骤
一定要用完即关闭,否则会有链接泄露,链接就无法获取了
·
对jdbc封装,因为程序中不可能处处都有五大步骤 DBHelper 【执行增删改,执行查询,释放资
源方法】
·
增加数据库连接池,优化链接,提升性能【参数调优,mysql也可以设置连接相关参数】
·
问题-》resultset无法转换
·
可以使用数据库元数据+反射 自定义封装类
·
apache有一个 DBUtils 专门处理 结果集到java对象转换【把这个代码研读】
·
问题->sql要编写,没有缓存,多表查询得结果无法自动映射
`
上框架
mybatis 自动化得ORM
hibernate全自动ORM框架

3.Fastjson

美化输出Java对象。

 <!--alibaba json工具,他和mybatis没关系-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>

4.基于MyBatis的DAO开发(传统)

在上一篇博客中,其实已经写了传统DAO开发的步骤,现在可以在写一次,发现它的问题。

提供一个工具类,保证SqlSessionFactory全局唯一。

之前我们通过mybatis API,做了一个封装类,MybatisUtil封装了一个全局的sessionfactory
编写了Mapper的实现类。

package com.oracle.util;


import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;


/**
 * 编写mybatis的工具类,工具类最核心的就是为程序中各个mapper
 * 提供 sqlsession ,因为mapper要和 xml 打交道,打交道进行crud操作
 * 都是使用的sqlsession 提供的方法
 * <p>
 * 如何保证sqlsessionfactory是系统唯一呢?
 * 所谓单例 整个应用中,仅有一个类的实例存在
 */
public class MybatisUtil {

    //私有化构造
    private MybatisUtil() {
    }

    //避免线程的不可见行
    private volatile static SqlSessionFactory sqlSessionFactory;

    public static SqlSessionFactory getSqlSessionFactory() {
        //双重锁
        if (sqlSessionFactory == null) {

            synchronized (MybatisUtil.class) {
                if (sqlSessionFactory == null) {
                    try {
                        //在这里初始化即可
                        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
                        sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

                    } catch (Exception e) {
                        System.err.println("初始化sqlsessionfactory异常:" + e.getMessage());
                        System.exit(0);
                    }
                }
            }
        }
        return sqlSessionFactory;

    }
}

实际开发中,我们应该有DAO类,封装CRUD方法。我们现在可以利用MyBatis提供的编程接口来代替JavaWeb阶段的方法。在MyBatis中DAO用Mapper类代替。

AccountMapper.java

package com.oracle.mapper;

import com.oracle.model.Account;
import com.oracle.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;


/*
* 运行期间通过反射动态生成实现类
*
* */


import java.util.List;

public class AccountMapper {
    public List<Account> selectAll(){
        SqlSession sqlSession=MybatisUtil.getSqlSessionFactory().openSession();
        try {
            //查询多个用selectList,查询一个用selectOne
            return sqlSession.selectList("com.oracle.mapper.AccountMapper.selectAll");
        }finally {
            sqlSession.commit();
            sqlSession.close();
        }
    }

    public Account selectById(Integer aid){
        SqlSession sqlSession=MybatisUtil.getSqlSessionFactory().openSession();
        try {
            return sqlSession.selectOne("com.oracle.mapper.AccountMapper.selectById",aid);
        }finally {
            sqlSession.close();
        }
    }

    public void insert(Account account){
        SqlSession sqlSession=MybatisUtil.getSqlSessionFactory().openSession();
        try {
            sqlSession.insert("com.oracle.mapper.AccountMapper.insert",account);
            sqlSession.commit();
        }finally {
            sqlSession.close();

        }
    }

    public void update(Account account) {
        SqlSession sqlSession=MybatisUtil.getSqlSessionFactory().openSession();
        try {
            sqlSession.update("com.oracle.mapper.AccountMapper.update",account);
            sqlSession.commit();
        }finally {
            sqlSession.close();
        }
    }

    public void delete(Integer aid) {
        SqlSession sqlSession = MybatisUtil.getSqlSessionFactory().openSession();
        try {
            sqlSession.delete("com.oracle.mapper.AccountMapper.delete",aid);
            sqlSession.commit();
        }finally {
            sqlSession.close();
        }

    }

    public List<Account> selectByName(String aname){
        SqlSession sqlSession=MybatisUtil.getSqlSessionFactory().openSession();
        try {
            return sqlSession.selectList("com.oracle.mapper.AccountMapper.selectByName",aname);

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

}

存在的问题:

	以上代码存在的问题是 在java代码中需要硬编码sqlid,才能够获取mapper中的sql文件。还有就是每
一个方法的开头和结构都是 获取 session,finally中关闭session。像这样的一些代码,我们可以通过模
板方法(代理方式)实现。



可以看出,以上很多代码是冗余的,在代码中嵌入了大量的sql ID。

    那么有没有一种方法,可以将Mapper接口与mapper.xml进行对应,而避免编写Mapper接口的实现类,从而避免去关联mapper.xml中的namespace + id。

    其实是可以的,只要遵循几个规则:

    文件名(AccountMapper.java、AccountMapper.xml)
    方法名
    参数
    返回值
    接口全路径,对应mapper.xml的namespace
    如果一一对应了,那Mapper实现类还有存在的必要吗?

		没有了,因为我们可以JDK动态代理(JDK的动态代理—只可以代理接口),生成接口的代理类。(基于接口的代理)

mybatis中提供Mapper接口开发模式

这种模式的意思是:
	编写一个接口。比如AccountMapper
	再编写一个AccountMapper.xml
	符合一定的要求自会后,在程序中我们可以直接调用接口的方法编程,而无需关注接口的实现类,因
	为mybatis帮我们‘自动’完成了 查找sql 的功能。

具体Mapper接口开发模式要符合什么要求呢?

	1、接口的名称和mapper.xml文件的名称保持一致,比如
 AccountMapper.java 接口 AccountMapper.xml 文件
	2、接口的全路径作为 AccountMapper.xml 中namespace的值
	3、接口中的方法名称 为AccountMapper.xml中 sql的 id值
	4、接口中方法的参数类型和AccountMapper.xml中定义的 resultType 、parameterType 保持一致
 保持一致,具体是指 parameterType 可以省略不屑,mybatis会根据TypeHandle根据传入的
参数类型自动推定。
查询方法接口中定义的是集合类型,但是xml文件中定义别的是集合中的某一条数据的类型
(resultType设置的是单条结果数据类型)。
java中的定义接口中的方法可以重载,但是在Mapper映射中不可以重载。
5、 在mybatis.xml中扫描mapper接口包,加载xml文件,或者逐一加载 接口全路径 都可以。

5.推荐mapper代理开发

我们只需要编写mapper接口和mapper.xml映射文件。

mybatis框架下mapper接口与对应xml文件之间的互相跳转,下载插件:

Free MyBatis Plugin

Mapper代理开发注意事项,必须严格遵循:

  • mapper.xml中的namespace必须和接口的全路径保持一致【因为mybatis要为接口动态产生代理
    对象,当执行接口中的方法,代理对象就会查找对应的mapper文件,mapper文件与接口如何对应
    呢?就需要namaspace】
  • mapper中的方法名id必须和接口中的方法名保持一致
  • 因为接口中的方法是可以被重载的,所以mapper中的sql id 对应的返回值类型,和参数类型需要
    与接口中保持一致。接口中如果返回值是集合,xml中还是集合中元素的数据类型,(接口中的方法可以重载,但是xml文件不可以重载)
  • 一定要将mapper接口【注册】交给mybaits

在mybatis-config.xml中配置:

  <!--加密mapper的映射文件-->
    <mappers>
        <!--mapper文件的全路径,注意使用/分隔-->
        <!--<mapper resource="com/oracle/mapper/AccountMapper.xml"></mapper>-->

        <!--class加载mapper接口,多个mapper每一个都需要配置,推荐使用包扫描-->
       <!-- <mapper class="com.oracle.mapper.AccountMapper"></mapper>-->


        <!--package包扫描,加载的是接口(mapper代理模式)-->

        <package name="com.oracle.mapper"/>
    </mappers>

6.步骤

1、AccountMapper.java接口

package com.oracle.mapper;

import com.oracle.model.Account;
import com.oracle.search.QuerySearchVO;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;

import java.security.AccessControlContext;
import java.util.List;
import java.util.Map;

public interface AccountMapper {

    List<Account> queryAllAccount();

    Account selectById(Integer aid);

    Account SelectByName(String name);
    Account SelectByName2(String name);

    void insert(Account account);

    void update(Account account);

    void delete(Integer aid);

}

2、AccountMapper.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">


<!--mapper就是映射的意思-->

<!--namespace 命名空间,程序中通过 sql映射的唯一标识获取 sql,比如当前文件有一个 insert sql标识,但是
其他文件中也有一个insert sql标识,就无法区分了,所以不同的sql功能,操作,使用namespce进行区分-->
<!--建议namaspce的值 为 包名.文件名-->
<mapper namespace="com.oracle.mapper.AccountMapper">
    <!--查询  -->
    <!--sql代码-->
    <!--mybatis中提供了四个最基础的sql标签,增删改查-->
    <!--id是程序中获取sql的标识,完整的标识是 namespace.sqlID-->
    <!--resultType 结果返回类型-->
    <select id="selectAll" resultType="account">

        select aid,aname,apassword,a_nickname as anickname from account
    </select>

    <!--通过id查询,#{} 占位符,程序会自动根据参数的类型,选择是否增加 '' -->
    <select id="selectById" resultType="account">
        select aid, aname, apassword, a_nickname as anickname
        from account
        where aid = ${value}
    </select>

    <select id="selectByName" resultType="account" >
        select aid, aname, apassword, a_nickname as anickname
        from account
        where aname like #{aname}
    </select>

    <select id="selectByName2" resultType="account">
    select aid, aname, apassword, a_nickname  as  anickname
    from account
    where aname like '%${value}%'
</select>
    
    <!--下面的insert语句中 #{} 中写的是对象的属性-->
    <!--useGeneratedKeys 使用插入后的主键值-->
    <!--keyProperty将主键值映射到哪一属性上-->
    <!--主键自动获取前提是 数据库的主键生成方式 为 自动增长 mysql,sqlserver。 oracle不是的-->
   <insert id="insert" useGeneratedKeys="true" keyProperty="aid">
       insert
       into
       account(aname,apassword,a_nickname)
       values(#{aname},#{apassword},#{anickname})
   </insert>

    <update id="update">
        update account
        set aname = #{aname}, apassword = #{apassword}, a_nickname = #{anickname}
        where aid = #{aid}
    </update>

    <delete id="delete">
        delete from account
        where aid = #{aid}
    </delete>



</mapper>

3、测试

package com.oracle.mapper;


import com.alibaba.fastjson.JSON;
import com.oracle.model.Account;
import com.oracle.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/*
*
* 一旦有类调用,就通过this交给当前类处理
* */
public class AccountMapperTest {
    @Test
    public void test(){
        SqlSession sqlSession = MybatisUtil.getSqlSessionFactory().openSession();

        //mybatis 动态生成AccountMapper接口的实现类
        AccountMapper mapper = sqlSession.getMapper(AccountMapper.class);

        List<Account> accounts = mapper.selectAll();
        System.out.println(JSON.toJSONString(accounts,true));

        System.out.println("==============================");
        Account account = mapper.selectById(1);
        System.out.println(JSON.toJSONString(account,true));

        System.out.println("==============================");

        Account account1 = new Account();
        account1.setAname("林冲");
        account1.setApassword("123456");
        account1.setAnickname("豹子头");

        mapper.insert(account1);

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




    }
}

在这里插入图片描述

注意事项:
使用Mapper代理方式进行mybatis开发,需要将mapper接口交给mybatis。

五. JDK动态代理

在SE阶段学了JDK的动态代理,现在在MyBatis阶段,再给出JDK动态代理的方式。

代理设计模式:

程序:完成一个业务,有核心业务和非核心业务之分。所谓代理设计模式,就是将核心业务与非核心业务相分离。

代理的本质就是将核心业务与非核心业务分离,代码容易维护,减少冗余,让程序具有更高的扩展性。

代理设计模式中的几个概念:

  • 目标角色:target 被代理者 (卖房者)
  • 代理角色:paoxy 代理角色 (中介)
  • 抽象角色:是指目标和代理的共同约束
  • 客户端:调用者
抽象角色 js = new 代理角色(目标角色);

//调用方法后,代理角色方法就会执行
js.xxx();

代理的Java实现

  • 静态代理
    • 只要有一个目标类,就要写一个代理类,类过多,繁琐
  • 动态代理
    • 基于接口的Proxy,要求被代理类和目标类都要实现接口

2.代理的演示

接口:

package com.oracle.mapper;

import com.oracle.model.Account;

public interface JieKou {
    void register(Account account);

    void show();
}

在service层下编写实现类

package com.oracle.service;

import com.oracle.mapper.JieKou;
import com.oracle.model.Account;

public class JieKouImpl implements JieKou {
    public void register(Account account) {
        System.out.println("接口的实现类保存数据了");
    }

    public void show() {
        System.out.println("接口的实现类查询数据并展示了");
    }
}

在service层下编写代理工具类,注意要实现InvocationHandler接口

package com.oracle.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ProxyUtil implements InvocationHandler {
    //被代理类对象
    private Object obj;

    public ProxyUtil(Object obj) {
        this.obj = obj;
    }

    public Object getProxy() {
        //第三个参数为InvocationHandler接口的实现类对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }

    /**
     * 当访问被代理类的方法时,程序来到该invoke()方法处
     * @param proxy 代理对象
     * @param method 方法对象
     * @param args 方法的参数
     * @return
     * @throws Throwable
     */

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理,来增强被代理类的方法逻辑
        Object result = null;

        long start = System.currentTimeMillis();
        System.out.println("===" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "正在访问" + method + "===");
        //调用目标类的方法
        result = method.invoke(obj, args);

        long end = System.currentTimeMillis();
        System.out.println("程序耗时" + (end - start) / 1000 + "s.");
        return result;
    }
}

测试:

package com.oracle.mapper;

import com.oracle.model.Account;
import com.oracle.service.JieKouImpl;
import com.oracle.service.ProxyUtil;
import org.junit.Test;

public class ProxyTest {
    @Test
    public void test(){
        JieKouImpl jieKou = new JieKouImpl();
        JieKou proxy = (JieKou) new ProxyUtil(jieKou).getProxy();
        proxy.register(new Account(5,"wuhu","wwww","芜湖"));
        proxy.show();
        /*
        
           ===2020-09-08 13:47:18正在访问public abstract void com.oracle.mapper.JieKou.register(com.oracle.model.Account)===
            接口的实现类保存数据了
            程序耗时0s.
            ===2020-09-08 13:47:18正在访问public abstract void com.oracle.mapper.JieKou.show()===
            接口的实现类查询数据并展示了
            程序耗时0s.
       
        * */
    }
}

JDK生成的动态代理类:

  • extends Proxy类,implements 被代理类实现的接口;
  • 正是由于Java不支持多继承,所以动态代理类已经继承了Proxy类,则不能则继承其他类,所以JDK的动态代理只能代理接口,而不能代理类;

JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了。

五.CGLib代理

JDK的动态代理,只能用来代理接口。

而使用cglib可以代理类。(基于类的动态代理)

CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法,这样也可以保证代理类拥有目标类的同名方法。(CGLib无法代理final的方法)

1.步骤

1、引入依赖

<dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

2、在JDK动态代理中提供一个Proxy类来创建代理类,而在CGLib动态代理中也提供了一个类似的类Enhancer(增强)

3、MethodInterceptor:方法拦截器

package com.oracle.service;

import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ProxyCGLibUtil implements Callback {
    //目标类对象
    private Object obj;


    public ProxyCGLibUtil(Object obj) {
        this.obj = obj;
    }

    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        //代理类要继承obj的类
        enhancer.setSuperclass(obj.getClass());
        
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //o表示代理对象,method表示目标类中的方法,args表示方法参数,proxy表示代理方法的MethodProxy对象
                return methodProxy.invokeSuper(o, objects);
            }
        });
        return enhancer.create();
        //生成动态代理类
    }
}

4.测试

package com.oracle.mapper;

import com.oracle.service.JieKouImpl;
import com.oracle.service.ProxyUtil;
import org.junit.Test;

public class ProxyCglibTest {
    @Test
    public void test(){
        JieKouImpl jieKou = new JieKouImpl();
        JieKou proxy = (JieKou) new ProxyUtil(jieKou).getProxy();
        System.out.println(proxy);

        System.out.println("==============");
        proxy.register(null);
        proxy.show();

        /*
        * 
        *
        ===2020-09-08 14:03:31正在访问public java.lang.String java.lang.Object.toString()===
        程序耗时0s.
        com.oracle.service.JieKouImpl@3581c5f3
        ==============
        ===2020-09-08 14:03:31正在访问public abstract void com.oracle.mapper.JieKou.register(com.oracle.model.Account)===
        接口的实现类保存数据了
        程序耗时0s.
        ===2020-09-08 14:03:31正在访问public abstract void com.oracle.mapper.JieKou.show()===
        接口的实现类查询数据并展示了
        程序耗时0s.

        * 
        * 
        * */
    }
}

生成CGLib动态代理生成的代理类的class文件。

 @Test
    public void test2(){
        //在指定目录下生成动态代理类
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\IdeaProjects\\mybatis07\\target\\classes\\com\\oracle\\service");

        ProxyCGLibUtil util = new ProxyCGLibUtil(new JieKouImpl());
        JieKou proxy = (JieKou) util.getProxy();

        System.out.println(proxy.getClass().getName());
        proxy.register(null);
        proxy.show();
        /*
        *
        *com.oracle.service.JieKouImpl$$EnhancerByCGLIB$$1a10e3f1
        接口的实现类保存数据了
        接口的实现类查询数据并展示了
        * */
    }

我们指定的目录下面看一下生成的字节码文件,有三个,一个是代理类的FastClass,一个是代理类,一个是目标类的FastClass,

中间那个是代理类,第一个是代理类的FastClass,第三个是目标类的FastClass。

在这里插入图片描述

package com.oracle.service;

import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.westos.model.Account;

public class AccountService$$EnhancerByCGLIB$$f91ee6ca extends AccountService implements Factory {
    //......
    
    //重写父类的方法
    @Override
    public final void register(Account var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$register$0$Method, new Object[]{var1}, CGLIB$register$0$Proxy);
        } else {
            super.register(var1);
        }
    }
    
    @Override
    public final void show() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$show$1$Method, CGLIB$emptyArgs, CGLIB$show$1$Proxy);
        } else {
            super.show();
        }
    }
    
    //equals()、hashCode()、toString()、clone()
    
    final void CGLIB$register$0(Account var1) {
        super.register(var1);
    }
    
    final void CGLIB$show$1() {
        super.show();
    }
    
    final boolean CGLIB$equals$2(Object var1) {
        return super.equals(var1);
    }
    //hashCode、toString、clone
    
    //newInstance()、set/getCallback

Jdk动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。
FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值