Java-Mybatis-02

学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1NE411Q7Nx

学习资料:mybatis 参考文档 – https://mybatis.org/mybatis-3/zh/index.html

1、Mybatis 配置解析

1.1、核心配置文件

  • mybatis-comfig.xml
  • Mybatis的配置文件包含了影响Mybatis行为的设置和属性信息。
  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)

        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

1.2、环境配置

资源配置环境。mybatis-config.xml 可以配置多个环境, 通过 default去选择环境.

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境

通过default选择环境:

即:选择 development的资源配置环境 :

<environments default="development">

选择 test 的资源配置环境 :

<environments default="test">

MyBatis默认的事务管理器就是JDBC ,连接池:POOLED

对于事务管理器(transactionManager:在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]")

<!--XML 配置文件或者预先定义的一个 config,去SqlSessionFactoryBuilder, 配置数据库,便于创建sqlSessionFactory实例-->
<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/mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment><environment id="test"><transactionManager type="JDBC"/> <!--事务管理器--><dataSource type="POOLED"> <!--默认的连接池--><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--关联映射文件--><mappers><mapper resource="com/AL/dao/UserMapper.xml"/></mappers>
</configuration>

1.3、属性 properties

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.poperties】

配置文件:db.properties:

driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8
username = root
password = 123456

有了db.properties后,那么此时可以去简化 xml 资源配置文件。

注意:在xml资源配置文件中,需要注意这些参数的顺序 级别.所有的标签都规定了顺序.

顺序为:

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>
<properties resource="db.properties"><!--  在 db.properties  属性配置文件中定义了这些 sql 配置,可以不在这里的xml配置文件中定义。如果定义的话, 会以 xml资源配置文件中的 (引入外部配置文件) 为基准,优先级比较高<property name="username" value="root"/><property name="pwd" value="111"/>-->
</properties>

对于上面所示的在 mybatis-config.xml配置文件中 进行 引用 属性配置信息 db.properties的时候:

  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的

mybatis-config.xml 配置文件代码:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"><!--XML 配置文件或者预先定义的一个 config,去SqlSessionFactoryBuilder, 配置数据库,便于创建sqlSessionFactory实例--><configuration><properties resource="db.properties" /><!--可以给实体类起别名--><typeAliases><typeAlias type="com.AL.pojo.User" alias="User"/></typeAliases><environments default="development">  <!-- 定义默认的环境配置,使用default选择环境 development--><environment id="development"><transactionManager type="JDBC"/> <!--事务管理器--><dataSource type="POOLED"> <!--数据源、默认的连接池--><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment><environment id="test"><transactionManager type="JDBC"/> <!--事务管理器--><dataSource type="POOLED"> <!--默认的连接池--><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--关联映射文件--><mappers><mapper resource="com/AL/dao/UserMapper.xml"/></mappers>
</configuration>

注意:当配置文件是上述的 db.properties: 时候,此时运行测试发生了错误:

org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: java.sql.SQLNonTransientConnectionException: Cannot load connection class because of underlying exception: com.mysql.cj.exceptions.WrongArgumentException: Malformed database URL, failed to parse the connection string near ';useUnicode=true&amp;charsetEncoding=UTF-8'.

错误的意思是 数据库 url 格式错误,无法解析,在这个 “useUnicode=true” 附近。

将properties属性配置文件修改如下:

driver = com.mysql.jdbc.Driver
#url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8
#url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8
url = jdbc:mysql://localhost:3306/mybatis
username = root
password = 123456

运行测试 test 的查询。发现可以正确查询,结果如下:

User{id=1, name='鑫仔', pwd='123456'}
User{id=2, name='天啊', pwd='123456'}
User{id=3, name='好胖', pwd='123890'}
User{id=4, name='jack', pwd='123456'}
User{id=5, name='clearlove', pwd='77777'}
User{id=6, name='the shy', pwd='123456'}

属性properties 和 xml环境配置中的优先级

如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:【加载顺序如下:最后加载的优先级最高】

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

测试:

db.properties:

driver = com.mysql.jdbc.Driver
#url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8
url = jdbc:mysql://localhost:3306/mybatis
username = root
password = 123456

mybatis-config.xml:

    <environments default="development">  <!-- 定义默认的环境配置,使用default选择环境 development--><environment id="development"><transactionManager type="JDBC"/> <!--事务管理器--><dataSource type="POOLED"> <!--数据源、默认的连接池--><property name="driver" value="${driver}"/>
<!--                <property name="url" value="${url}"/>--><property name="url" value="jdbc:mysql://localhost:3306/jdbc"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment>

测试的结果为 jdbc数据库中的user 表:

User{id=1, name='烤羊腿', pwd='null'}
User{id=3, name='烤鸭', pwd='null'}

1.4、类型别名

类型别名(typeAliases)

分析:
select * from user 等价于 select id,name,pwd from user;

mybatis会根据数据库的字段名去找对应的实体类的属性名,(他会将所有列名转换为小写,然后去找实体类中对应的 set方法 ,set方法后面的字段就对应数据库的字段名;如果不一样就会返回null)

  • 类型别名可为 Java 类型设置一个短的名字。 它仅用于 XML 配置,意在降低冗余的完全限定类名书写。

    例如:

    在xml配置文件 mybatis-config.xml中 对实体类起别名:

    <typeAliases><typeAlias type="com.AL.pojo.User" alias="User"/>
    </typeAliases>
    

    此时我们需要在对应的mapper.xml中进行修改:返回的已经不是 resultType="com.AL.pojo.User"

        <select id="getUserList" resultType="user">select * from mybatis.user</select>
    

    测试:

    public class UserDaoTest {@Testpublic void test(){//1.拿到sqlSessionFactory对象//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;List<User> userList = userMapper.getUserList(); //查询全部用户//获得结果集for (User user : userList) {System.out.println(user);}//关闭sqlSessionsqlSession.close();}
    }
    

    结果为:

    User{id=1, name='鑫仔', pwd='123456'}
    User{id=2, name='天啊', pwd='123456'}
    User{id=3, name='好胖', pwd='123890'}
    User{id=4, name='jack', pwd='978654321'}
    User{id=5, name='clearlove', pwd='77777'}
    
  • 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

    <typeAliases><package name="com.AL.pojo"/>
    </typeAliases>
    

    每一个在包 com.AL.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.AL.pojo.User 的别名为 user;若有注解,则别名为其注解值。见下面的例子:

    @Alias("hello")
    public class User {...
    }
    

此时的mapper.xml文件中的 映射返回类型修改:sql语句的修改

    <select id="getUserList" resultType="hello">select * from mybatis.user</select>
  • 在实体类少的时候,使用第一种; 在实体类多的时候,建议使用第二种
  • 第一种直接对 类 进行别名设置,可以DIY; 在第二种时是对包中的Java Bean进行小写别名,如果DIY,需要自己在实体类中增加注解。

1.5、映射器(mappers)

关于SQL语句的映射方式:

MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

<!-- 使用相对于类路径的资源引用 -->
<mappers><mapper resource="org/mybatis/builder/AuthorMapper.xml"/><mapper resource="org/mybatis/builder/BlogMapper.xml"/><mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers><mapper url="file:///var/mappers/AuthorMapper.xml"/><mapper url="file:///var/mappers/BlogMapper.xml"/><mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers><mapper class="org.mybatis.builder.AuthorMapper"/><mapper class="org.mybatis.builder.BlogMapper"/><mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers><package name="org.mybatis.builder"/>
</mappers>

自己的理解:

  • resources和java属于一个级别的,那么 使用相对于类路径的资源引用 就是

    因为是资源引用,所以使用 resource,类路径吗,所以就是 com/AL/dao 这种路径的形式。

  • 使用映射器接口实现类的完全限定类名

  • 将包内的映射器接口实现全部注册为映射器:

MapperRegistry: 注册绑定我们的Mapper文件。

  1. 我们在xml配置文件中定义了 配置 congiguration ,能够去得到实例SqlSessionFactoryBuilder, 用它创建一个SqlSessionFactory

    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
  2. 定义我们的实体类 User, 和数据库表格相匹配;

  3. 工具类的创建,MybatisUtils中构建 SqlSessionFactory 实例,获得sqlSession

  4. 创建接口 UserMapper(SQL方法接口),利用mapper.xml文件完成(接口实现类) 接口方法的映射,sql 语句功能,这两者标签一一对应。 即 id 和 接口中的方法名对应

  5. 在测试代码中 完成sqlSession 的调用测试,完成CRUD的具体参数传送,与mapper、mapper.xml 之间的方法 标签 一一对应。 因为就是去调用的接口实现类 sql中的方法,当然方法名一样了。

  6. SQL映射语句的定义, 在 UserMapper.xml中完成了UserMapper 接口方法的实现。 而在mybatis-config.xml中的sql语句映射,即sql是对哪里的文件进行使用。

<mapper namespace="com.AL.dao.UserMapper"><select id="getUserList" resultType="com.AL.pojo.User">select * from mybatis.user</select>

或者是说, sql 文件中的数据进行调用的时候, 我们去哪里寻找这些 sql 语句。这时的办法是在配置文件 config中进行映射, 在这里你已经配置了数据库, 然后再加上数据库的 sql语句调用的 映射位置

添加映射文件时,同样要注意在配置中的标签顺序。

有以下几种方法:

方法一:使用相对于类路径的资源引用 ,调用资源文件 resource

<!--关联映射文件-->
<mappers><mapper resource="com/AL/dao/UserMapper.xml"/>
</mappers>

方法二:使用映射器接口实现类的完全限定名, 即使用class 文件去绑定注册

<!--使用映射器接口实现类的完全限定类名-->
<mappers><mapper class="com.AL.dao.UserMapper"/>
</mappers>

方式三: 将包内的映射器接口实现全部注册为映射器。 直接将整个包进行绑定注册,映射

<!--将包内的映射器接口实现全部注册为映射器 -->
<mappers><package name="com.AL.dao"/>
</mappers>

注意点

  • 接口和它的Mapper配置文件必须同名。 即接口和接口实现类(映射,sal语句) 文件名必须一致
  • 接口和它的Mapper配置文件必须在同一个包下!。 即接口和接口实现类在一个包里面

2、生命周期和作用域

Mybatis框架流程:

SqlSessionFactoryBuilder:

我们使用 SqlSessionFactoryBuilder的目的 就是为了去创建 SqlSessionFactory实例。构建实例完成后 就不需要了。 所以把它放在方法作用域, 在使用这个方法的时候 去进行调用,然后用完就可以丢了。 得到 SqlSessionFactoryBuilder 是通过预定义 mybatis-config.xml 配置文件的。

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
  • 最佳作用域是方法作用域(也就是局部方法变量)。

SqlSessionFactory:

SqlSessionFactory 数据库 session工厂, 这个的作用应该就类似于数据库连接池,连接和关闭数据库配置信息。有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。所以在你创建进行使用的时候 会一直伴随你这个应用结束关闭为止, 最佳作用域就是应用作用域(ApplocationContext)

  • 可以把它想象为数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • SqlSessionFactory 的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接到 连接池的一个请求
  • 每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后应该赶快关闭,否则资源被占用。

每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它

这里面的每一个mapper,就代表一个事务

3、解决属性名和字段名不一致的问题

例子:

数据库中的字段:

实体类中的属性:

此时进行查询 id=1:

    @Testpublic void getUserById(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User userById = mapper.getUserById(1);System.out.println(userById);sqlSession.close();}

结果为: 可以发现结果中,password为 null。 因为数据库中表格的字段为pwd,和实体类中的属性password不一致。

User{id=1, name='鑫仔', password='null'}

解决方法:针对属性名和字段名不一致的解决方法为:

3.1、改别名

即利用sql语句中 起别名的方式,使得pwd对应位 password。 如下所示,在mapper.xml中 改变sql语句,

    <select id="getUserById" parameterType="int" resultType="com.AL.pojo.User"><!--select * from mybatis.user where id = #{id}-->select id, name, pwd as password from mybatis.user where id = #{id}</select>

3.2、结果集映射

resultMap:[推荐使用的方法]

类似于下面这样一一对应起来: 类似于前面使用的 万能map。

1 id name pwd
2 id name passwodr

resultMap 元素是 MyBatis 中最重要最强大的元素。

ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

    <!--结果集映射 --><resultMap id="UserMap" type="User"><!--column数据库中的字段, property实体类中的属性--><result column="id" property="id"/><result column="name" property="name"/><result column="pwd" property="password"/></resultMap><select id="getUserById" resultMap="UserMap">select * from mybatis.user where id = #{id}</select>

ResultMap 的优秀之处——你完全可以不用显式地配置它们。

不用显式地配置它们,需要哪一个,映射哪一个就可以了:

<!--结果集映射 -->
<resultMap id="UserMap" type="User"><!--column数据库中的字段, property实体类中的属性--><result column="pwd" property="password"/>
</resultMap>

对于属性名和字段名不一致的问题的 maven项目: 整个流程回顾:

3.2.1、此时创建的数据库为

CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB CHARSET=utf8;
INSERT INTO `user`(`id`, `name`, `pwd`)
VALUES (1,'鑫仔','123456'), (2,'天啊','123456'),(3,'好胖','123890')

3.2.2、mybatis-config.xml核心配置文件:

预先定义的一个 config,产生SqlSessionFactoryBuilder, 配置数据库以及连接,便于创建sqlSessionFactory实例,还需要有 mappers即关联映射文件(SQL语句进行运行时去查找的 实体类,即对应着的数据库 表 字段名,就是你想要的那个 数据表):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"><!--XML 配置文件或者预先定义的一个 config,去SqlSessionFactoryBuilder, 配置数据库,便于创建sqlSessionFactory实例-->
<configuration><properties resource="db.properties"><!--  在 db.properties  属性配置文件中定义了这些 sql 配置,可以不在这里的xml配置文件中定义。如果定义的话, 会以 xml资源配置文件中的 (引入外部配置文件) 为基准,优先级比较高<property name="username" value="root"/><property name="pwd" value="111"/>--></properties><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/mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--关联映射文件--><mappers><mapper resource="com/AL/dao/UserMapper.xml"/></mappers><!--使用映射器接口实现类的完全限定类名<mappers><mapper class="com.AL.dao.UserMapper"/></mappers>--><!--将包内的映射器接口实现全部注册为映射器<mappers><package name="com.AL.dao"/></mappers>-->
</configuration>

3.2.3、MybatisUtils工具类

创建这个sqlSessionFactory,是从资源文件resource(mybatis-config.xml)中去获得的。然后再从中获得 SqlSession 的实例

package com.AL.utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;//import javax.annotation.Resources;// sqlSessionFactory --> sqlSession
public class MybatisUtils {// 创建这个sqlSessionFactory,是从资源文件resource中的去获得。private static SqlSessionFactory sqlSessionFactory;static {try {//利用mybatis第一步: 获取一个sqlSessionFactory对象String resource = "mybatis-config.xml";/**  注意:不需要修改,是因为导入的包不正确.不是 import javax.annotation.Resources* 将ResourcesgetResourceAsStream(resource);改为* Resources.class.getResourceAsStream(resource);*  //得到配置文件流* InputStream inputStream = Resources.class.getResourceAsStream(resource);*/InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (Exception e) {e.printStackTrace();}}//有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。public static SqlSession getSqlSession() {//SqlSession sqlSession = sqlSessionFactory.openSession();//return sqlSession;return sqlSessionFactory.openSession();}
}

3.2.4、实体类 User

对应着数据库表中的 user数据,不过此时的实体类User的属性名字和 数据库中的字段名不一致:pwd 与 password。

package com.AL.pojo;public class User {private int id;private String name;private String password;public User(int id, String name, String password) {this.id = id;this.name = name;this.password = password;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", password='" + password + '\'' +'}';}
}

3.2.5、dao(Mapper)接口

  • userMapper接口
package com.AL.dao;import com.AL.pojo.User;import java.util.List;
import java.util.Map;public interface UserMapper {// 获取全部的用户List<User> getUserList();// 根据ID查找用户. 利用 map 映射User getUserById2(Map<String,Object> map); // map
}
  • userMapper.xml 接口实现类:完成映射,sql功能。 此时利用了 resultMap 结果集映射,可以解决属性名和字段名不一致的问题。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace= 绑定一个对应的Dao/Mapper接口
<mapper namespace="org.mybatis.example.BlogMapper"><select id="selectBlog" resultType="Blog">select * from Blog where id = #{id}
-->
<mapper namespace="com.AL.dao.UserMapper"><!--结果集映射 --><resultMap id="UserMap" type="com.AL.pojo.User"><!--一般通过id标签来映射主键column = 数据库的列名property = 结果集对应的数据库列名的映射名--><result column="id" property="id"/><result column="name" property="name"/><result column="pwd" property="password"/></resultMap><select id="getUserById2" resultMap="UserMap">select * from mybatis.user where id = #{id};</select><!--select 查询语句 --><select id="getUserList" resultType="com.AL.pojo.User">select * from mybatis.user</select>
</mapper>

3.2.6、测试

通过sqlSessionFactory对象,工具实体类, 拿到一个sqlSession实例。

通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象;

创建一个HashMap,来通过结果集映射去获得查询的结果

package com.AL.dao;import com.AL.pojo.User;
import com.AL.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;import java.util.HashMap;
import java.util.List;public class UserDaoTest {@Testpublic void test(){//1.拿到sqlSessionFactory对象//SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSession();//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。//SqlSession sqlSession = sqlSessionFactory.openSession();SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userDao = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;List<User> userList = userDao.getUserList();//获得结果集for (User user : userList) {System.out.println(user);}//关闭sqlSessionsqlSession.close();}@Testpublic void getUserById2(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);HashMap<String, Object> map = new HashMap<String, Object>();map.put("id", 1);User userById2 = mapper.getUserById2(map);//获得结果System.out.println(userById2);sqlSession.close();}
}

结果:

User{id=1, name='鑫仔', password='123456'}

4、日志

如果我们在进行数据库操作的时候,出现了异常,需要排错,检查错误。那么,此时,日志就是最好的助手

Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging
  • STDOUT_LOGGING
  • NO_LOGGING

MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。

你想在Mybatis中具体使用哪个日志实现,在设置中进行设定。设置名为 logImpl。

4.1、STDOUT_LOGGING

STDOUT_LOGGING:标准日志输出

注意细节:字体的大小写, 不能有空格

测试例子:在mybatis.xml文件中配置标准日志:注意此时setting 在configuration 中的顺序位置

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

输出结果为:

Opening JDBC Connection  //打开JDBC连接
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
Created connection 1997859171. //创建连接
Setting autocommit to false on JDBC Connection  [com.mysql.cj.jdbc.ConnectionImpl@7714e963]
==>  Preparing: select * from mybatis.user where id = ?
==> Parameters: 5(Integer)
<==    Columns: id, name, pwd
<==        Row: 5, clearlove, 77777
<==      Total: 1
User{id=5, name='clearlove', pwd='77777'} //寻找的结果
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7714e963]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7714e963]
Returned connection 1997859171 to pool.  //关闭事务,连接池。

4.2、Log4j

Log4j:

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

注意细节

字体的大小写, 不能有空格

测试例子1: 使用日志文件 Log4j

  • 导入 Log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>
  • 在mybatis-config.xml中配置 log4j 为日志的实现,注意此时setting 在configuration 中的顺序位置
<settings><setting name="logImpl" value="LOG4J"/>
</settings>
  • 定义log4j.properties 资源配置文件:

```xml
- ### Log4j配置 ###
#定义log4j的输出级别和输出目的地(目的地可以自定义名称,和后面的对应)
#[ level ] , appenderName1 , appenderName2
log4j.rootLogger=DEBUG,console,file#-----------------------------------#
#1 定义日志输出目的地为控制台
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
####可以灵活地指定日志输出格式,下面一行是指定具体的格式 ###
#%c: 输出日志信息所属的类目,通常就是所在类的全名
#%m: 输出代码中指定的消息,产生的日志具体信息
#%n: 输出一个回车换行符,Windows平台为"/r/n",Unix平台为"/n"输出日志信息换行
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n#-----------------------------------#
#2 文件大小到达指定尺寸的时候产生一个新的文件
log4j.appender.file = org.apache.log4j.RollingFileAppender
#日志文件输出目录
log4j.appender.file.File=log/info.log
#定义文件最大大小
log4j.appender.file.MaxFileSize=10mb
###输出日志信息###
#最低级别
log4j.appender.file.Threshold=ERROR
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n#-----------------------------------#
#3 druid
log4j.logger.druid.sql=INFO
log4j.logger.druid.sql.DataSource=info
log4j.logger.druid.sql.Connection=info
log4j.logger.druid.sql.Statement=info
log4j.logger.druid.sql.ResultSet=info#4 mybatis 显示SQL语句部分
log4j.logger.org.mybatis=DEBUG
#log4j.logger.cn.tibet.cas.dao=DEBUG
#log4j.logger.org.mybatis.common.jdbc.SimpleDataSource=DEBUG
#log4j.logger.org.mybatis.common.jdbc.ScriptRunner=DEBUG
#log4j.logger.org.mybatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG
#log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  • 测试: 注意,导入的包为 import org.apache.log4j.Logger;
package com.AL.dao;import com.AL.pojo.User;
import com.AL.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;import java.util.List;public class UserDaoTest {static Logger logger = Logger.getLogger(UserDaoTest.class);@Testpublic void testLog4j(){logger.info("info: 进入了testLog4J");logger.debug("debug: 进入了testLog4J");logger.error("error: 进入了testLog4J");}@Testpublic void test(){//1.拿到sqlSessionFactory对象//SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSession();//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;List<User> userList = userMapper.getUserList(); //查询全部用户//获得结果集for (User user : userList) {System.out.println(user);}//关闭sqlSessionsqlSession.close();}@Test //插入public void addUser(){//1.拿到sqlSessionFactory对象//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;int res = userMapper.addUser(new User(4,"the shy","123456"));if (res>0){System.out.println("我插入成功了啊");}//提交事务//提交事务//提交事务sqlSession.commit();//关闭sqlSessionsqlSession.close();
}

5、分页

在mysql中,已经学过有关分页的处理,使用 limit 关键字。

分页的主要作用:减少数据的处理量。

使用 Limit分页:语法如下所示:

//语法:SELECT * FROM user LIMIT   stratIndex,pageSize
SELECT * FROM user LIMIT 0,2;
SELECT * FROM user LIMIT 3;  #[0,n]

在使用 SELECT * FROM user LIMIT 3; 时,会默认从0开始。

结果为:

1    鑫仔  1234562 天啊  1234563 好胖  123890

5.1、我们使用Mybatis来完成分页的功能

  1. Dao接口:采用map映射
package com.AL.dao;
import com.AL.pojo.User;
import java.util.List;
import java.util.Map;public interface UserMapper {// 获取全部的用户List<User> getUserList();//分页List<User> getUserLimit(Map<String, Integer> map);
}

2.mapper映射,sql语句:

<select id="getUserLimit" parameterType="map" resultType="com.AL.pojo.User">select * from mybatis.user limit #{startIndex},#{pageSize}
</select>

3.测试:

package com.AL.dao;import com.AL.pojo.User;
import com.AL.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;import java.util.HashMap;
import java.util.List;public class UserDaoTest {static Logger logger = Logger.getLogger(UserDaoTest.class);@Testpublic void testLog4j(){logger.info("info: 进入了testLog4J");logger.debug("debug: 进入了testLog4J");logger.error("error: 进入了testLog4J");}//分页@Testpublic void getUserByLimit(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);HashMap<String, Integer> map = new HashMap<String, Integer>();map.put("startIndex",1);map.put("pageSize",2);List<User> userLimit = mapper.getUserLimit(map);for (User user : userLimit) {System.out.println(user);}sqlSession.close();}@Testpublic void test(){//1.拿到sqlSessionFactory对象//SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSession();//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;List<User> userList = userMapper.getUserList(); //查询全部用户//获得结果集for (User user : userList) {System.out.println(user);}//关闭sqlSessionsqlSession.close();}
}

分页的结果为:表格中索引的顺序从 0 开始。

User{id=2, name='天啊', pwd='123456'}User{id=3, name='好胖', pwd='123890'}

对应的如果将 limit 的数字修改:

        map.put("startIndex",1);map.put("pageSize",1);

结果就是:User{id=2, name=‘天啊’, pwd=‘123456’}

5.2、RowBounds分页

1.接口:

public interface UserMapper {// 获取全部的用户List<User> getUserList();//分页List<User> getUserLimit(Map<String, Integer> map);//分页2List<User> getUserByRowBounds();
}

2.接口实现类。 mapper.xml配置完成映射 sql语句

<mapper namespace="com.AL.dao.UserMapper"><!--select 查询语句 --><!--<select id="getUserList" resultType="com.AL.pojo.User">select * from mybatis.user</select>--><!--分页--><select id="getUserLimit" parameterType="map" resultType="com.AL.pojo.User">select * from mybatis.user limit #{startIndex},#{pageSize}</select><!--使用RowBounds分页--><select id="getUserByRowBounds" parameterType="map" resultType="com.AL.pojo.User">select * from mybatis.user</select><!--获得所有用户--><select id="getUserList" resultType="com.AL.pojo.User">select * from mybatis.user</select>

3.测试:使用java方法: 注意,在选择selection方法的时候,选择里面会有 参数类型为bounds的

package com.AL.dao;import com.AL.pojo.User;
import com.AL.utils.MybatisUtils;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;import java.util.HashMap;
import java.util.List;public class UserDaoTest {static Logger logger = Logger.getLogger(UserDaoTest.class);@Testpublic void testLog4j(){logger.info("info: 进入了testLog4J");logger.debug("debug: 进入了testLog4J");logger.error("error: 进入了testLog4J");}@Testpublic void getUserByRowBounds(){SqlSession sqlSession = MybatisUtils.getSqlSession();// RowBounds 实现RowBounds rowBounds = new RowBounds(1, 2);List<User> userList = sqlSession.selectList("com.AL.dao.UserMapper.getUserByRowBounds",null,rowBounds);for (User user : userList) {System.out.println(user);}sqlSession.close();}@Testpublic void getUserByLimit(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);HashMap<String, Integer> map = new HashMap<String, Integer>();map.put("startIndex",1);map.put("pageSize",2);List<User> userLimit = mapper.getUserLimit(map);for (User user : userLimit) {System.out.println(user);}sqlSession.close();}@Testpublic void test(){//1.拿到sqlSessionFactory对象//SqlSessionFactory sqlSessionFactory = MybatisUtils.getSqlSession();//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;List<User> userList = userMapper.getUserList(); //查询全部用户//获得结果集for (User user : userList) {System.out.println(user);}//关闭sqlSessionsqlSession.close();}
}

结果:

User{id=2, name='天啊', pwd='123456'}User{id=3, name='好胖', pwd='123890'}

6、使用注解开发

6.1、面向接口编程

在开发中,会很多时候选择面向接口编程。原因:解耦,可拓展,提高复用。在分层开发中,上层不用管具体的实现,大家都会遵守共同的标准,使得开发变得容易,规范性更好。

在面向对象的系统中,系统的各种功能由不同的对象协助完成。此时,不会关注各自对象内部如何完成。 各个对象之间的协作称为系统设计的关键。不同模块之间的交互,这正是面向接口编程的思想。

三个面向的区别联系

  • 面向过程:考虑问题时,以一个具体流程即 事务过程为单位,去实现它;
  • 面向对象:考虑问题时,以对象为单位,考虑它的属性及方法。各自对象内部采用具体的面向过程思想去解决
  • 面向接口:更多的体现在对系统整体的架构,不同模块之间的交互

抽象类和接口有什么不同?

抽象类即使用修饰符 abstract、 接口为 interface。

abstract修饰符即可以修饰方法也可以修饰类,修饰类的时候叫抽象类,修饰方法的时候叫做抽象方法。

不同点

  1. 抽象类中可以去定义构造函数,接口不能定义构造函数。(即有参构造器。无参构造器。总会默认存在无参构造器的)

  2. 抽象类中既可以有抽象方法,也可以有具体方法;接口中只能有抽象方法(在Java8中,接口可以带有具体实现的方法,使用default修饰,这类方法就是默认方法)

  3. 抽象类中的成员权限可以是public、default、protected(抽象类中的抽象方法就是为了进行重写,所以不用 private进行修饰);接口中的成员只能是public(方法默认就是:public abstract,成员变量默认:public static final,因为要被继承)

  4. 抽象类中可以包含静态方法,接口中不能。 但是在JDK1.8以后,接口中就能包括静态方法了。 原先不能原先是由于 接口不能实现方法(静态方法必须实现)。现在可以使用接口直接调用静态方法。

    接口的成员变量默认是public static final。 static定义后,将变量存储在静态区域,由于一个类中可以实现多个接口,所以为了防止重名,将其定义为static 存储在静态区域,一旦重复就会报错。【静态区域:方法区:常量池和 类变量】

    之所以是 final,是因为接口被大家共同调用,它的变量不能随便更改。

JDK1.8中对接口增加了新的特性

(1)默认方法(default method):JDK 1.8允许给接口添加非抽象的方法实现,但必须使用default关键字修饰;定义了default的方法可以不被实现子类所实现,但只能被实现子类的对象调用;如果子类实现了多个接口,并且这些接口包含一样的默认方法,则子类必须重写默认方法;

(2)静态方法(static method):JDK 1.8中允许使用static关键字修饰一个方法,并提供实现,称为接口静态方法。接口静态方法只能通过接口调用(接口名.静态方法名)

相同点:

  1. 都不能被实例化。【即我们需要去完成接口实现类,抽象类的完成】
  2. 可以将抽象类和接口作为引用类型
  3. 一个类如果继承了某个抽象类或者实现了某个接口,就必须对其中所有的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
  4. 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。

什么时候该用接口什么时候该用抽象类

参考链接: https://blog.csdn.net/wb_snail/article/details/78862533

  • 接口的本质是 多态。 定义的抽象方法在不同类中有不同的实现方式
  • 抽象类的本质是 多态 + 复用。 继承那么就有代码的复用性。 不用每次都再一次去实现一次抽象方法了。
  • 面向接口编程:那么自己遇到的最多的场景就是 MVC架构中的 业务层 service层。 接口+接口实现类即可
  • 抽象类编程:抽象类中的方法可以有实现的具体方法,那么这个方法 子类就可以直接继承后使用,它的复用性。抽象类的抽象方法,和接口一样,不同的子类对象去实现自己想要完成的具体形式,它的多态性。

抽象类就相当于一个模板,模板中有子类可以公用的部分,也有需要子类自行实现的部分,是为模板式设计,它同时具有多态性+复用性;

接口是对行为的抽象,它只定义一组行为规范,每个实现类都要实现所有规范,叫辐射式设计,它只有多态性;

  • 抽象类是复用,可以有方法;接口是重写规范,但是也有默认(default)的方法(有方法体),不必重写这种方法

使用注解开发: 对于原来的mybatis。是需要使用xml配置的,完成映射,sql语句,接口实现类的工作。而注解,可以去代替一些xml的配置

例子:

1.接口的修改:且不要mapper.xml 映射配置文件。不需要接口实现类了。 直接注解开发,实现此sq功能。

public interface UserMapper {// 获取全部的用户@Select("select * from mybatis.user")List<User> getUserList();
}

2.在resource里面的 mybatis-config.xml配置文件进行修改

既然原来的mapper.xml已经没有映射配置文件,那么这里的注册的 映射也不要,反而是进行绑定接口;

注意: 在这个mappers中进行关联映射文件,绑定接口时, 同一个接口名只能在此注册一次,不然会报错。 所以,我将原来的写为了注释。

<!--关联映射文件-->
<mappers><!--<mapper resource="com/AL/dao/UserMapper.xml"/>--><!--绑定接口--><mapper class="com.AL.dao.UserMapper"/>
</mappers>

3.测试。 获得sqlSession对象,然后对接口中的方法进行测试。

@Test
public void getUser(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> user = mapper.getUser();for (User user1 : user) {System.out.println(user);}sqlSession.close();
}

结果为:

[User{id=1, name='鑫仔', pwd='123456'}, User{id=2, name='天啊', pwd='123456'}, User{id=3, name='好胖', pwd='123890'}, User{id=4, name='jack', pwd='978654321'}, User{id=5, name='clearlove', pwd='77777'}]

对于注解开发:

本质: 利用的是 反射机制实现

底层: 动态代理

回顾:注解

来源链接:

https://mp.weixin.qq.com/s?__biz=MzAxMjY1NTIxNA==&mid=2454441897&idx=1&sn=729688d470c94560c1e73e79f0c13adc&chksm=8c11e0a8bb6669be1cc4daee95b221ba437d536d598520d635fac4f18612dded58d6fddb0dce&scene=21#wechat_redirect

https://blog.csdn.net/yuzongtao/article/details/83306182

什么是注解?

注解也叫做 元数据。例如@Override和@Deprecate(标明某个类或方法过时)。注解用于对代码的说明,可以去对包、类、接口、字段(属性把)、方法参数、局部变量等进行注解。可以分为三类:

  • Java自带的注解 即内置注解:@Override、@Deprecated、@SuppressWarnings
  • 元注解:用于对注解进行定义的注解。@Retention、@Target、@Inherited、
  • 自定义注解。自己定义名称。对于 注解类 的时候,前面的修饰符为 @Interface。

注解的用途

在看注解的用途之前,有必要简单的介绍下XML和注解区别,

  • 注解:是一种分散式的元数据,与源代码紧绑定。
  • xml:是一种集中式的元数据,与源代码无绑定

当然网上存在各种XML与注解的辩论哪个更好,这里不作评论和介绍,主要介绍一下注解的主要用途:

  1. 生成文档,通过代码里标识的元数据生成javadoc文档。
  2. 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  3. 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  4. 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例

注解使用演示

这边总共定义了4个注解来演示注解的使用

  1. 定义一个可以注解在Class,interface,enum上的注解,
  2. 定义一个可以注解在METHOD上的注解
  3. 定义一个可以注解在FIELD上的注解
  4. 定义一个可以注解在PARAMETER上的注解

具体代码如下:

/*** 定义一个可以注解在Class,interface,enum上的注解*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnTargetType {/*** 定义注解的一个元素 并给定默认值* @return*/String value() default "我是定义在类接口枚举类上的注解元素value的默认值";
}/*** 定义一个可以注解在METHOD上的注解*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnTargetMethod {/*** 定义注解的一个元素 并给定默认值* @return*/String value() default "我是定义在方法上的注解元素value的默认值";
}/*** 定义一个可以注解在FIELD上的注解*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnTargetField {/*** 定义注解的一个元素 并给定默认值* @return*/String value() default "我是定义在字段上的注解元素value的默认值";
}/*** 定义一个可以注解在PARAMETER上的注解*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnTargetParameter {/*** 定义注解的一个元素 并给定默认值* @return*/String value() default "我是定义在参数上的注解元素value的默认值";
}

编写一个测试处理类处理以上注解:

/*** 测试java注解类** @author zhangqh* @date 2018年4月22日*/
@MyAnTargetType
public class AnnotationTest {@MyAnTargetFieldprivate String field = "我是字段";@MyAnTargetMethod("测试方法")public void test(@MyAnTargetParameter String args) {System.out.println("参数值 === "+args);}public static void main(String[] args) {// 获取类上的注解MyAnTargetTypeMyAnTargetType t = AnnotationTest.class.getAnnotation(MyAnTargetType.class);System.out.println("类上的注解值 === "+t.value());MyAnTargetMethod tm = null;try {// 根据反射获取AnnotationTest类上的test方法Method method = AnnotationTest.class.getDeclaredMethod("test",String.class);// 获取方法上的注解MyAnTargetMethodtm = method.getAnnotation(MyAnTargetMethod.class);System.out.println("方法上的注解值 === "+tm.value());// 获取方法上的所有参数注解  循环所有注解找到MyAnTargetParameter注解Annotation[][] annotations = method.getParameterAnnotations();for(Annotation[] tt : annotations){for(Annotation t1:tt){if(t1 instanceof MyAnTargetParameter){System.out.println("参数上的注解值 === "+((MyAnTargetParameter) t1).value());}}}method.invoke(new AnnotationTest(), "改变默认参数");// 获取AnnotationTest类上字段field的注解MyAnTargetFieldMyAnTargetField fieldAn = AnnotationTest.class.getDeclaredField("field").getAnnotation(MyAnTargetField.class);System.out.println("字段上的注解值 === "+fieldAn.value());} catch (Exception e) {e.printStackTrace();}}
}

运行的结果如下:

类上的注解值 === 我是定义在类接口枚举类上的注解元素value的默认值
参数上的注解值 === 我是定义在参数上的注解元素value的默认值
参数值 === 改变默认参数
方法上的注解值 === 测试方法
字段上的注解值 === 我是定义在字段上的注解元素value的默认值

反射

正常方式为: 引入需要的"包类"名称,即导入jar包 --> 通过new实例化 --> 取得实例化对象

反射方式为:实例化对象 --> getClass()方法 --> 得到完整的"包类"名称

原文链接:https://blog.csdn.net/weixin_42298270/article/details/113371164

反射机制:

Java反射机制是在运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取类信息以及动态调用对象内容就称为Java语言的反射机制。

简单的来说,反射机制指的是程序在运行时能够获取自身的信息。在Java中,只要给定类的名字, 那么就可以通过反射机制来获得类的所有信息。【运行时加载,动态地创建你想要的实例

反射的实现:

要使用一个类,就要先把它加载到虚拟机中,生成一个Class对象。这个Class对象就保存了这个类的一切信息。反射机制的实现,就是获取这个Class对象,通过Class对象去访问类、对象的元数据以及运行时的数据。

1、获取Class对象的三种方式

  • 使用 Class 类中的 forName()静态方法
  • 调用某个对象的 getClass()方法(需要new对象)
  • 调用某个类的 class 属性来获取该类对应的 Class 对象

2、创建Class对象的两种方法

  • Class 对象的 newInstance()。使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求该 Class 对象对应的类有默认的空构造器。
  • 调用 Constructor 对象的 newInstance()。先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。

Java中为什么要用反射机制?直接创建对象不就可以了吗,我觉得这主要涉及到了动态与静态的问题。

  • 静态编译:在编译时确定类型,绑定对象,即通过。
  • 动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以**降低类之间的耦合性**。

反射的优点缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性。【因为你能获得这个类所有的信息】
缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是 慢于 直接执行相同的操作。

例子: 反射, 反射中获取Class类的实例可以采用这种语法:Class c = Class.forName(“java.lang.String”)。 在下面提到了获取Class类的实例的几种方法。

在这里,我们创建了一个实体类 User, 然后利用反射去实例化对象。 并去观察这几个实例化对象的 hashCode是否一样。

一个类在内存中只有一个Class对象,类被加载后,它的整个结构都会被封装在 Class 对象中。

package com.AL.Reflection;//什么叫反射
public class Test01 {public static void main(String[] args) throws ClassNotFoundException {Class c1 = Class.forName("com.AL.Reflection.User");System.out.println(c1);Class c2 = Class.forName("com.AL.Reflection.User");Class c3 = Class.forName("com.AL.Reflection.User");Class c4 = Class.forName("com.AL.Reflection.User");// 一个类在内存中只有一个class对象// 一个类被加载后,类的整个结构都被封装在class对象中System.out.println(c2.hashCode()); //观察hashCode是否一样System.out.println(c3.hashCode());System.out.println(c4.hashCode());}
}
// 实体类  表示:pojo, entity
class User{private String name;private int id;private int age;public User() {}public User(String name, int id, int age){this.name = name;this.id = id;this.age = age;}public String getName(){return name;}public void setName(String name) {this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", id=" + id +", age=" + age +'}';}@Overridepublic int hashCode() {return super.hashCode();}
}

例子: 通过反射, 动态的创建对象

在这里,我们采用Class类的forName() 方法去获取一个 Class 类的实例, 包名+类名。

有了Class对象, 我们可以采用 newInstance()方法去创建一个具体的实例化的对象。 注意:这种方法调用的是无参构造器,你创建的User类中默认会有一个无参构造器; 但是,如果你定义了有参构造器,默认的会失效,你必须显示的定义 添加一个无参构造器。

// 获得class对象
Class c1= Class.forName("com.AL.Reflection.User");
// 构造一个对象
User user1 = (User)c1.newInstance(); // 本质是调用了类的无参构造器   // 需要在User类中添加一个无参构造器
System.out.println(user1);

当不使用无参构造器时, 我们可以采用 getDeclaredConstructor() 方法去构建这个类中(此时为User类)的构造器, 需要在 () 这里面指定类中的形参类型。然后可以向构造器传递形参的具体元素数值。且这里是通过 Constructor 实例化对象

Constructor constructor= c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User)constructor.newInstance("ALZN",001,10);

通过反射, 动态的创建对象例子完整代码:

package com.AL.Reflection;import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;// 动态的创建对象, 通过反射
public class Test08 {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException, InstantiationException {// 获得class对象Class c1= Class.forName("com.AL.Reflection.User");// 构造一个对象User user1 = (User)c1.newInstance(); // 本质是调用了类的无参构造器   // 需要在User类中添加一个无参构造器System.out.println(user1);// 通过构造器创建对象Constructor constructor= c1.getDeclaredConstructor(String.class, int.class, int.class);User user2 = (User)constructor.newInstance("ALZN",001,10);System.out.println(user2);//通过反射调用普通方法User user3 = (User)c1.newInstance();//通过反射类获取一个方法Method setName = c1.getDeclaredMethod("setName", String.class);// invoke: 激活的意思// (对象, "方法的值")setName.invoke(user3,"ALZN");System.out.println(user3.getName());//通过反射操作属性System.out.println("##################");User user4 = (User)c1.newInstance();Field name = c1.getDeclaredField("name");//不能直接操作私有属性, 我们需要关闭安全检测,属性或方法的 setAccessible(true)name.setAccessible(true);name.set(user4,"ALZNZN");System.out.println(user4.getName());}
}

结果:

User{name='null', id=0, age=0}
User{name='ALZN', id=1, age=10}
ALZN
##################
ALZNZN

注意:

  • 通过反射调用方法时,需要激活该方法:
//通过反射调用普通方法
User user3 = (User)c1.newInstance();
//通过反射类获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
// invoke: 激活的意思       (对象, "方法的值")
setName.invoke(user3,"ALZN");
System.out.println(user3.getName());
  • 通过反射操作私有属性时,需要关闭安全检测:
//通过反射操作属性
User user4 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性, 我们需要关闭安全检测,属性或方法的 setAccessible(true)
name.setAccessible(true);
name.set(user4,"ALZNZN");
System.out.println(user4.getName());

反射操作注解

反射操作注解:

  • getAnnotations
  • getAnnotation

了解一下 ORM, 即 Object relationship Mapping 对象关系映射。

我们后面在 数据库 表 和 java的实体类中 属性对应起来:

  • 类和表的结构对应起来。即类 class Student 和这个数据库中的表 student 名字对应
  • 属性和字段对应。 类中的 属性 id name age 和表中的字段对应起来。
  • 对象和记录对应。 实例化对象后的数据信息 和 表中的记录 即表里面的信息 对应起来。

反射操作注解的例子:

package com.AL.Reflection;import java.lang.annotation.*;
import java.lang.reflect.Field;//反射操作注解
public class Test12 {public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {Class c1 = Class.forName("com.AL.Reflection.Student2");//通过反射获得注解Annotation[] annotations = c1.getAnnotations();for (Annotation annotation : annotations) {System.out.println(annotation);}//获得注解的value值TableNote tableNote = (TableNote) c1.getAnnotation(TableNote.class);System.out.println(tableNote.value());//获得并打印注解的值//获得类指定的注解Field name = c1.getDeclaredField("name");//要获得的类型FieldNote annotation = name.getAnnotation(FieldNote.class);//获取值的方法System.out.println(annotation.columnName()+annotation.type()+annotation.length());}
}@TableNote("db_student")
class Student2{@FieldNote(columnName = "db_id",type = "int",length = 10)private int id;@FieldNote(columnName = "db_age",type = "int",length = 10)private int age;@FieldNote(columnName = "db_name",type = "String",length = 3)private String name;public Student2() {}public Student2(int id, int age, String name) {this.id = id;this.age = age;this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Student2{" +"id=" + id +", age=" + age +", name='" + name + '\'' +'}';}
}//类注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableNote{String value();
}//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldNote{String columnName();String type();int length();
}

结果:

@com.AL.Reflection.TableNote(value="db_student")
db_student
db_nameString3

自己理解的 注解和反射的关系:

使用注解,无论是java中的自带标准注解 @Override 重写 还是利用元注解以及自定义注解,去创建一个注解的类。对这个类使用注解进行开发,指定这个注解对象能够修饰的范围、存在的作用域,当然这个里面的属性、方法是自己定义或者实现的。 我们使用注解 让这个注解类 具有它的意义、属性值。

注解开发是什么呢? 其实就是利用反射机制获取注解类中的 注解信息,然后也能利用反射去获取或者 自己定义属性信息。 这样的话 就能解释 @Bean的作用。

@Bean 利用注解开发,反射机制去获取这个类的配置信息,将这个类的实例 注入到容器Bean中。 这里面获取自己想要的类的 一个具体实例,就用到了 set()方法 属性注入, invoke()方法是反射中对方法的一个操作。

6.2、Mybatis执行流程分析

创建 sqlSessionFactory 对象的时候,java程序作了什么工作? 点击查看源码

build函数所做的事情:

在构造sqlSessionFactory实例化前的 build 会解析配置文件流xmlConfigBuilder,Configuration所有的配置信息。


从上面的这个图片也可以看出:mybatis的加载数据库资源的顺序为:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

Mybatis详细执行流程

6.3、注解增删查改

我们可以在工具类创建的时候,实现自动提交事务。

使用注解进行 sql,并选择自动提交事务

1.在实体工具类。 mybatisUtiles 工具类修改为自动提交事务。 就不需要自己每次手动提交事务了。

注意:在sqlSessionFactory.openSession这个方法 选择其中参数带有 boolean的。

package com.AL.utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.InputStream;//import javax.annotation.Resources;// sqlSessionFactory --> sqlSession
public class MybatisUtils {// 创建这个sqlSessionFactory,是从资源文件resource中的去获得。private static SqlSessionFactory sqlSessionFactory;static {try {//利用mybatis第一步: 获取一个sqlSessionFactory对象String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (Exception e) {e.printStackTrace();}}//有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。// SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。public static SqlSession getSqlSession() {//return sqlSessionFactory.openSession();return sqlSessionFactory.openSession(true); //在sqlSessionFactory.openSession这个方法 选择其中参数带有 boolean的。}
}

2.接口 mapper文件的修改。在接口中使用注解完成sql语句,实现该方法下的功能

方法中存在多个参数时,所有的参数前面必须添加 @Param(“id”) 注解

注意:在这里我要使用注解开发完成 mybatis中的 CRUD, 所以 Mapper.xml 映射文件应该删除。 不然这个xml映射文件中的接口实现类的方法对应的 id 名即方法会和注解此时能够完成的方法名发生冲突,导致在测试中发生错误。

错误类似于:Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for

CRUD代码:

package com.AL.dao;import com.AL.pojo.User;
import org.apache.ibatis.annotations.*;import java.util.List;
import java.util.Map;public interface UserMapper {//注解开发@Select("select * from mybatis.user")List<User> getUsers();// 根据ID查找用户// 方法中存在多个参数时,所有的参数前面必须添加 @Param("id")  注解@Select("select * from mybatis.user where id = #{id}")User getUserById(@Param("id") int id); //依据的是 id,所以用 int id//insert 一个用户@Insert("insert into user(id,name,pwd) values (#{id}, #{name}, #{pwd})")int addUser(User user); //插入一个用户,即插入了一个具体的实例,对象,所以用 user//修改一个用户@Update("update user set name=#{name}, pwd=#{pwd} where id = #{id}")int updateUser(User user);//删除一个用户@Delete("delete from user where id = #{id}")int deleteUser(int id);
}

3.测试:此时的增删改事务,不用自己手动进行提交事务 就可以完成。

package com.AL.dao;import com.AL.pojo.User;
import com.AL.utils.MybatisUtils;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;import java.util.HashMap;
import java.util.List;public class UserDaoTest {static Logger logger = Logger.getLogger(UserDaoTest.class);@Testpublic void testLog4j(){logger.info("info: 进入了testLog4J");logger.debug("debug: 进入了testLog4J");logger.error("error: 进入了testLog4J");}@Testpublic void getUsers(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);List<User> user = mapper.getUsers();for (User user1 : user) {System.out.println(user);}sqlSession.close();}@Test //查询public void getUserById(){//1.拿到sqlSessionFactory对象//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;User userById = userMapper.getUserById(2);//获得结果System.out.println(userById);//关闭sqlSession}@Test //插入public void addUser(){//1.拿到sqlSessionFactory对象//2.通过sqlSessionFactory对象openSession()创建一个sqlSession。SqlSession sqlSession = MybatisUtils.getSqlSession();//3.通过sqlSession获得mapper对象 , 参数为映射文件对应的接口类的class对象UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//4.通过mapper对象来执行操作;int res = userMapper.addUser(new User(4,"jack","123456"));if (res>0){System.out.println("我插入成功了啊");}//提交事务//提交事务//提交事务sqlSession.commit();//关闭sqlSession}@Test //更新public void updateUser(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);userMapper.updateUser(new User(4, "jack", "321"));sqlSession.commit(); //提交事务}@Test //删除public void deleteUser(){SqlSession sqlSession = MybatisUtils.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);userMapper.deleteUser(4);sqlSession.commit(); //提交事务}
}

其中某个部分的结果:查看数据库的 user 表为

1    鑫仔  123456
2   天啊  123456
3   好胖  123890
4   jack    123456
5   clearlove   77777
6   the shy 123456

注意点

在mybatis中,无论是用 mapper.xml接口实现类,还是注解开发 去完成接口中方法对应的 sql 功能,都需要在 核心配置文件 mybatis-config.xml中进行注册 绑定。

  • 在之前没有使用注解,使用接口实现类时:绑定的 映射的 方法为:
<!--关联映射文件-->
<mappers><mapper resource="com/AL/dao/UserMapper.xml"/>
</mappers>
  • 使用注解开发,进行绑定的接口类为:
<mappers><!--resource 对应的是一个接口类的映射文件--><!--class 绑定接口,对应的是一个接口类--><mapper class="com.AL.dao.UserMapper"/>
</mappers>
  • 接口和它的Mapper配置文件必须同名
  • 接口和它的Mapper配置文件必须要在同一个包下。

关于@Param

@Param注解用于给方法参数起一个名字。以下是总结的使用原则:

  • 在方法只接受一个参数的情况下,可以不使用@Param。
  • 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
  • 如果参数是 JavaBean , 则不能使用@Param。
  • 不使用@Param注解时,参数只能有一个,并且是Javabean。

原来的SQL语句:

delete from
ups_role_permission_dataparams
where role_id = #{roleId, jdbcType=INTEGER}

在这里用到了 #{}。 使用 #{} 时:

  • #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】

    INSERT INTO user (name) VALUES (#{name});
    INSERT INTO user (name) VALUES (?);
    
  1. 用来传入参数,sql在解析的时候会加上 " " 去当成字符串来解析,例如这里的 role_id = "roleid "
  2. #{} 能够在很大程度上防止 SQL注入
  • ${} 的作用是直接进行字符串替换

    INSERT INTO user (name) VALUES ('${name}');
    INSERT INTO user (name) VALUES ('kuangshen');
    

延伸:

  1. 在用 ${} 去传入数据是直接显示在生成的 sql中, 比如 role_id = #{roleId, jdbcType=INTEGER},在 sql中会被解析成 role_id = roleid,执行时会报错
  2. ${} 方式无法防止sql注入
  3. $ 一般用入传入数据库对象, 比如数据库表名
  4. 能用 #{} 就用 #{}。

注意:Mybatis中进行排序时使用 order by 动态参数时注意, 使用的是 ${} 而不是用 #{}。

7、Lombok的使用

Lombok的准备工作

  • 在IDEA中安装Lombok插件

打开file->setting, ->Plugins,然后搜索 Lombok,进行安装。如下所示:

  • maven导入依赖, jar包:
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.10</version>
</dependency>

例子: 使用Lombok这个包,然后可以使用注解开发。如下所示:

在一个实体类 User中 使用Data 注解,可以得到一系列 get/set、toString 等多种方法。

package com.AL.pojo;import lombok.Data;@Data
public class User {private int id;private String name;private String pwd;
}`

结果:可以观察到 多的这些方法:

Java-Mybatis(二): Mybatis配置解析、resultMap结果集映射、日志、分页、注解开发、Mybatis执行流程分析相关推荐

  1. Mybatis执行流程分析_自定义简易Mybatis框架

    自定义简易Mybatis框架 Mybatis执行流程分析 Mybatis代码编写流程: Mybatis配置文件加载过程: 需求分析及技术概述 根据上述的功能结构图, 得出如下需求: 1. 需要具有配置 ...

  2. Java多线程- 线程池的基本使用和执行流程分析 - ThreadPoolExecutor

    线程池的实现原理 池化技术 一说到线程池自然就会想到池化技术. 其实所谓池化技术,就是把一些能够复用的东西放到池中,避免重复创建.销毁的开销,从而极大提高性能. 常见池化技术的例如: 线程池 内存池 ...

  3. scala语言的底层是java实现的_Scala学习笔记一(与Java、Jvm的关系以及程序执行流程分析)...

    一.Scala语言与Java.Jvm的关系分析 Scala语言是马丁奥德斯基接触Java语言后,针对Java语言的特点,将函数式编程语言的特点融合到Java中,由此发明的.Scala语言和Java语言 ...

  4. 【网络安全】Metasploit生成的Shellcode的导入函数解析以及执行流程分析(2)

    密码破解的利器--彩虹表(rainbow table) 确定 shellcode 依赖于哪些导入将使研究人员进一步了解其其余逻辑.不用动态分析shellcode,并且考虑到研究人员已经弄清楚了上面的哈 ...

  5. (Mybatis)XML配置解析

    文章目录 XML配置解析 1.核心配置文件 2.环境配置(environments) 3. 属性(properties) 4. 设置(settings) 5. 其他配置 6. 映射器(mappers) ...

  6. 【狂神Mybatis笔记】配置解析

    核心配置文件:mybatis-config.xml Mybatis的配置文件包含了会影响Mybatis行为的设置和属性信息 环境配置(environments) 要求:学会使用配置多套运行环境 MyB ...

  7. mybatis源码学习篇之——执行流程分析

    前言 在正式学习mybatis框架源码之前,需要先弄懂几个问题?myabtis框架是什么?为什么需要mybatis框架?使用mybatis框架带来的好处是什么? 回答这几个问题之前,我们先来看一下,之 ...

  8. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)

    原文地址:http://www.cnblogs.com/dongying/p/4142476.html 最近太忙了,一直没时间继续更新博客,今天忙里偷闲继续我的Mybatis学习之旅.在前九篇中,介绍 ...

  9. mybatis删除成功返回0_你还在用分页?试试 MyBatis 流式查询,真心强大!

    转自:捏造的信仰 segmentfault.com/a/1190000022478915 基本概念 流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果.流式查询 ...

最新文章

  1. [JAVA EE] 拦截器
  2. 百行代码打造一个DI容器(支持瞬时生命周期、单利生命周期、构造函数自动注入、属性自动注入、字段自动注入)...
  3. python爬取天天基金_python多线程+代理池爬取天天基金网、股票数据过程解析
  4. NHibernate——Criteria条件查询
  5. 心态很容易受别人影响_为什么说缠论中的走势中枢容易影响短线买卖交易心态?...
  6. w ndows无法识别usb,电脑无法识别usb设备的解决方法
  7. 面试之 Python 进阶
  8. linux 打包库文件,Linux的文件的打包(tar方法)
  9. windows内核驱动漏洞挖掘工具 - IOCTL Fuzzer
  10. python class是什么_python中什么是类
  11. 在线字体文件转换工具(.ttf/.otf/.woff/.woff2文件在线转换)
  12. ognl.OgnlException: target is null for setProperty(null, offset, [Ljava.lang.String;@1667f3c) 解决方法
  13. Segment Tree Beats(吉司机线段树)
  14. Go函数和方法的区别
  15. 硬件知识:声控楼道灯电路
  16. NYOJ 237 NYOJ 239 二分图 最大匹配模板题 游戏高手的烦恼 月老的难题 两个题一样
  17. SpringBoot使用mybatis-autogenerator时,显示Failure to find org.eclipse.m2e:lifecycle-mapping:pom:1.0.0错误
  18. JDK1.8源码分析:可重入锁ReentrantLock和Condition的实现原理
  19. 关于22年应届生就业难发表一些自己的想法
  20. 第一天:外企面试英语口语常用语

热门文章

  1. CSS3中的一些新特性(CSS)
  2. 如何让编辑器运行你的代码
  3. vue 文档.PDF无法预览解决方法
  4. 【滴水逆向笔记】C语言指针
  5. 关于URL重写的实现
  6. 强烈建议收藏!达芬奇素材离线的六种解决方法
  7. 【c++从菜鸡到王者】第六篇:详解晦涩难懂的c++语法
  8. Apache Tuscany SCA 用户指南
  9. Python全栈学习笔记day 40.5+:线程池和线程池的Python标准模块--concurrent.futures
  10. 怎么看曲线有没有斜渐近线_曲线的斜渐近线怎么求啊?步骤是什么