MyBatis使用详解上篇我们手动开发了一个MyBatis项目,但是我们仅仅是编写了代码,对于整个项目是如何运行以及每个代码的意义都没有仔细的分析和说明,那么接下来我们就开始分析每个代码的意义以及如何编写这个代码 配置MyBatis全局配置文件要使用Mybatis来操作数据库,那么当然就需要配置数据库相关信息,这件需要在mybatis全局配置文件中进行。即全局配置的xml文件,其对整个MyBatis进行事务的支持、数据库的配置等信息的配置。我们一般放在main/resource文件中,如下所示 <configuration> <!-- 环境配置,可以配置多个环境 --> <environments default="chat01"> <!-- environment用来对某个环境进行配置 id:环境标识,唯一 --> <environment id="chat01"> <!-- 事务管理器工厂配置 --> <transactionManager type="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/> <!-- 数据源工厂配置,使用工厂来创建数据源 --> <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatisdemo?characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> </configuration> configuration元素这个是mybatis全局配置文件的根元素,每个配置文件只有一个 environments元素用来配置mybatis的环境信息,用来配置多个环境的,具体的一个环境使用environment元素进行配置,environment元素有个id用来标识某个具体的环境。 environments元素有个default属性,用来指定默认使用哪个环境,如上面默认使用的是chat01。 environment元素用来配置具体的环境信息,这个元素下面有两个子元素:transactionManager和dataSource
用来配置事务工厂的,有个type属性,type的值必须是org.apache.ibatis.transaction.TransactionFactory接口的实现类,用来创建事务管理器对象的,TransactionFactory接口默认有2个实现: org.apache.ibatis.transaction.managed.ManagedTransactionFactory org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory 一般情况下我们使用org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory这个,mybatis和其他框架集成,比如和spring集成,事务交由spring去控制。
这个用来配置数据源的,type属性的值必须为接口org.apache.ibatis.datasource.DataSourceFactory的实现类,DataSourceFactory也是一个工厂,用来创建数据源javax.sql.DataSource对象的,mybatis中这个接口默认有3个实现类: org.apache.ibatis.datasource.jndi.JndiDataSourceFactory org.apache.ibatis.datasource.pooled.PooledDataSourceFactory org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 我们使用第2个org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,这个用来创建一个数据库连接池类型的数据源,可以实现数据库连接共用,减少连接重复创建销毁的时间。 <property name="属性名称" value="值"/> 创建Mapper xml文件在mybatis中一般我们将一个表的所有sql操作写在一个mapper xml中,一般命名为XXXMapper.xml格式。 内容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "http:///dtd/mybatis-3-mapper.dtd"> <mapper namespace="zhonghu.mybatis.chat01.UserMapper"> </mapper> mapper xml根元素为mapper,这个元素有个namespace属性,系统中会有很多表,每个表对应一个Mapper xml,为了防止mapper文件重复,我们需要给每个mapper xml文件需要指定一个namespace,通过这个可以区分每个mapper xml文件,上面我们指定为zhonghu.mybatis.chat01.UserMapper。 一会对user表的所有操作相关的sql,我们都会写在上面这个xml中。 mybatis全局配置文件中引入Mapper xml文件user.xml我们写好了,如何让mybatis知道这个文件呢,此时我们需要在mybatis-config.xml全局配置文件中引入UserMapper.xml,在mybatis-config.xml加入下面配置: <mappers> <mapper resource="mapper/user.xml"/> </mappers> mappers元素下面有多个mapper元素,通过mapper元素的resource属性可以引入Mapper xml文件,resource是相对于classes的路径。 上面说的都是一些配置文件,配置文件都ok了,下面我们就需要将mybatis跑起来了,此时需要使用到mybatis中的一些java对象了。 构建SqlSessionFactory对象//指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //读取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //构建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSessionFactory是一个接口,是一个重量级的对象,SqlSessionFactoryBuilder通过读取全局配置文件来创建一个SqlSessionFactory,创建这个对象是比较耗时的,主要耗时在对mybatis全局配置文件的解析上面,全局配置文件中包含很多内容,SqlSessionFactoryBuilder通过解析这些内容,创建了一个复杂的SqlSessionFactory对象,这个对象的生命周期一般和应用的生命周期是一样的,随着应用的启动而创建,随着应用的停止而结束,所以一般是一个全局对象,一般情况下一个db对应一个SqlSessionFactory对象。 构建SqlSession对象SqlSession相当于jdbc中的Connection对象,相当于数据库的一个连接,可以用SqlSession来对db进行操作:如执行sql、提交事务、关闭连接等等,需要通过SqlSessionFactory来创建SqlSession对象,SqlSessionFactory中常用的有2个方法来创建SqlSession对象,如下: //创建一个SqlSession,默认不会自动提交事务 SqlSession openSession(); //创建一个SqlSession,autoCommit:指定是否自动提交事务 SqlSession openSession(boolean autoCommit); SqlSession接口中很多方法,直接用来操作db,方法清单如下,大家眼熟一下: <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); <E> List<E> selectList(String statement); <E> List<E> selectList(String statement, Object parameter); <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); <K, V> Map<K, V> selectMap(String statement, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); <T> Cursor<T> selectCursor(String statement); <T> Cursor<T> selectCursor(String statement, Object parameter); <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds); void select(String statement, Object parameter, ResultHandler handler); void select(String statement, ResultHandler handler); void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); int insert(String statement); int insert(String statement, Object parameter); int update(String statement); int update(String statement, Object parameter); int delete(String statement); int delete(String statement, Object parameter); void commit(); void commit(boolean force); void rollback(); void rollback(boolean force); List<BatchResult> flushStatements(); void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> type); Connection getConnection(); 上面以select开头的可以对db进行查询操作,insert相关的可以对db进行插入操作,update相关的可以对db进行更新操作。 引入lombok支持(非必须)Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。 Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。 lombok的使用步骤
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.10</version> <scope>provided</scope> </dependency>
引入logback(非必须)为了方便查看mybatis运行过程中产生的日志,比如:执行的sql、sql的参数、sql的执行结果等等调试信息,我们需要引入日志框架的支持,logback是一个很好的日志框架,此处我们就使用这个 mybatis中集成logback步骤
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <logger name="zhonghu" level="debug" additivity="false"> <appender-ref ref="STDOUT" /> </logger> </configuration>
写一个测试用例上面说了这么多,下面我们就写一个测试类来演示一下 内容如下: package zhonghu.mybatis.chat01; import lombok.extern.slf4j.Slf4j; 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 org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; @Slf4j public class UserMapperTest { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //读取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //构建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } @Test public void sqlSession() { SqlSession sqlSession = this.sqlSessionFactory.openSession(); log.info("{}", sqlSession); } } 上面代码中有个@Slf4j注解,这个是lombok提供的,可以在这个类中生成下面代码: private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserTest.class); 运行一下上面的用例:sqlSession方法,输出如下: 37:52.473 [main] INFO z.mybatis.chat01.UserMapperTest - org.apache.ibatis.session.defaults.DefaultSqlSession@2d127a61 至此我们就搭建了一个最小化的Mybatis项目,后面就需要我们根据需要编写我们的sql文件 使用SqlSesion执行sql操作SqlSession常见的用法SqlSession相当于一个连接,可以使用这个对象对db执行增删改查操作,操作完毕之后需要关闭,使用步骤:
如下所示 //获取SqlSession SqlSession sqlSession = this.sqlSessionFactory.openSession(); try { //执行业务操作,如:增删改查 } finally { //关闭SqlSession sqlSession.close(); } 上面我们将SqlSession的关闭放在finally块中,确保close()一定会执行。 新增操作需求:传入UserModel对象,然后将这个对象的数据插入到user表中。 创建一个UserModelUserModel类,代码如下: package zhonghu.mybatis.chat01; import lombok.*; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @ToString public class UserModel { private Long id; private String name; private Integer age; private Double salary; } 这个类的字段和user表对应。 user.xml中定义插入操作我们说过了,对user表的所有sql操作,我们都放在user.xml中,我们在user.xml中加入下面配置,使用insert元素定义插入操作: <!-- insert用来定义一个插入操作 id:操作的具体标识 parameterType:指定插入操作接受的参数类型 --> <insert id="insertUser" parameterType="zhonghu.mybatis.chat01.UserModel"> <![CDATA[ INSERT INTO user (id,name,age,salary) VALUES (#{id},#{name},#{age},#{salary}) ]]> </insert>
需要插入的值从UserModel对象中获取,取UserModel对象的的字段,使用#{字段}这种格式可以获取到UserModel中字段的值。 调用SqlSession.insert方法执行插入操作user插入的sql我们已经在UserMapper中写好,此时我们怎么调用呢? 需要调用SqlSession.insert方法: int insert(String statement, Object parameter) 这个方法有2个参数:
zhonghu.mybatis.chat01.UserMapper.insertUser
返回值为插入的行数。 UserTest类中新增一个测试用例: @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) { //创建UserModel对象 UserModel userModel = UserModel.builder().id(69L).name("Java冢狐").age(30).salary(50000D).build(); //执行插入操作 int result = sqlSession.insert("zhonghu.mybatis.chat01.UserMapper.insertUser", userModel); log.info("插入影响行数:{}", result); //提交事务 sqlSession.commit(); } } 运行输出如下: 05:15.831 [main] DEBUG z.m.chat01.UserMapper.insertUser - ==> Preparing: INSERT INTO user (id,name,age,salary) VALUES (?,?,?,?) 05:15.853 [main] DEBUG z.m.chat01.UserMapper.insertUser - ==> Parameters: 69(Long), Java冢狐(String), 30(Integer), 50000.0(Double) 05:15.951 [main] DEBUG z.m.chat01.UserMapper.insertUser - <== Updates: 1 05:15.952 [main] INFO z.mybatis.chat01.UserMapperTest - 插入影响行数:1
上面代码中创建SqlSession,我们使用的是sqlSessionFactory.openSession()创建的,这个方法创建的SqlSession,内部事务是非自动提交的方式,所以需要我们手动提交:
sqlSession.commit(); 如果想自动提交事务,在上面在创建SqlSession的时候改为sqlSessionFactory.openSession(true),指定事务为自动提交模式,所以最后我们不需要手动提交事务了。 更新操作需求:传入UserModel对象,然后通过id更新数据。 UserMapper.xml中定义Update操作使用update定义更新操作: <!-- update用来定义一个更新操作 id:操作的具体标识 parameterType:指定操作接受的参数类型 --> <update id="updateUser" parameterType="zhonghu.mybatis.chat01.UserModel"> <![CDATA[ UPDATE user SET name = #{name},age = #{age},salary = #{salary} WHERE id = #{id} ]]> </update> 写法和insert操作的写法类似,指定id标识、parameterType指定操作的参数类型,元素体中是具体的sql语句。 调用SqlSession.update方法执行更新操作需要调用SqlSession.update方法: int update(String statement, Object parameter) 这个方法有2个参数:
zhonghu.mybatis.chat01.UserMapper.updateUser
返回值为update影响行数。 UserTest类中新增一个测试用例: @Test public void updateUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //创建UserModel对象 UserModel userModel = UserModel.builder().id(1L).name("Java冢狐,你好").age(18).salary(5000D).build(); //执行更新操作 int result = sqlSession.update("zhonghu.mybatis.chat01.UserMapper.updateUser", userModel); log.info("影响行数:{}", result); } } 运行输出: 12:17.143 [main] DEBUG z.m.chat01.UserMapper.updateUser - ==> Preparing: UPDATE user SET name = ?,age = ?,salary = ? WHERE id = ? 12:17.163 [main] DEBUG z.m.chat01.UserMapper.updateUser - ==> Parameters: Java冢狐,你好(String), 18(Integer), 5000.0(Double), 1(Long) 12:17.258 [main] DEBUG z.m.chat01.UserMapper.updateUser - <== Updates: 1 12:17.258 [main] INFO z.mybatis.chat01.UserMapperTest - 影响行数:1 删除操作需求:根据用户的id删除对应的用户记录 UserMapper.xml中定义Delete操作使用update元素定义删除操作: <!-- update用来定义一个删除操作 id:操作的具体标识 parameterType:指定操作接受的参数类型 --> <update id="deleteUser" parameterType="java.lang.Long"> <![CDATA[ DELETE FROM user WHERE id = #{id} ]]> </update> 写法和update操作的写法类似,指定id标识、parameterType指定操作的参数类型,用户id为Long类型的,元素体中是具体的delete语句。 调用SqlSession.update方法执行更新操作需要调用SqlSession.delete方法: int delete(String statement, Object parameter) 这个方法有2个参数:
com.javacode2018.chat02.UserMapper.
返回值为delete影响行数。 UserTest类中新增一个测试用例: @Test public void deleteUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //定义需要删除的用户id Long userId = 1L; //执行删除操作 int result = sqlSession.delete("zhonghu.mybatis.chat01.UserMapper.deleteUser", userId); log.info("影响行数:{}", result); } } 运行输出: 14:26.711 [main] DEBUG z.m.chat01.UserMapper.deleteUser - ==> Preparing: DELETE FROM user WHERE id = ? 14:26.729 [main] DEBUG z.m.chat01.UserMapper.deleteUser - ==> Parameters: 1(Long) 14:26.811 [main] DEBUG z.m.chat01.UserMapper.deleteUser - <== Updates: 1 14:26.812 [main] INFO z.mybatis.chat01.UserMapperTest - 影响行数:1 执行查询select语句有恩多属性可以详细的配置每一条SQL语句
需求:查询所有用户信息 UserMapper.xml中定义Select操作<!-- select用来定义一个查询操作 id:操作的具体标识 resultType:指定查询结果保存的类型 --> <select id="getUserList" resultType="zhonghu.mybatis.chat01.UserModel"> <![CDATA[ SELECT * FROM user ]]> </select> 写法和update操作的写法类似,指定id标识、parameterType指定操作的参数类型,resultType指定查询结果的类型,元素体中是具体的select语句。 调用SqlSession.select方法执行更新操作UserTest添加一个用例: @Test public void getUserList() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { //执行查询操作 List<UserModel> userModelList = sqlSession.selectList("zhonghu.mybatis.chat01.UserMapper.getUserList"); log.info("结果:{}", userModelList); } } 多插入几行,然后运行上面的用例,输出如下: 16:00.798 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Preparing: SELECT * FROM user 16:00.817 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Parameters: 16:00.829 [main] DEBUG z.m.chat01.UserMapper.getUserList - <== Total: 16 16:00.829 [main] INFO z.mybatis.chat01.UserMapperTest - 结果:[UserModel(id=2, name=修改冢狐, age=23, salary=50000.0), UserModel(id=3, name=修改冢狐, age=24, salary=6666.66), UserModel(id=4, name=Mybatis-1, age=19, salary=10000.0), UserModel(id=5, name=Java冢狐-2, age=25, salary=20000.0), UserModel(id=6, name=Mybatis-2, age=20, salary=20000.0), UserModel(id=7, name=Java冢狐-3, age=26, salary=30000.0), UserModel(id=8, name=Mybatis-3, age=21, salary=30000.0), UserModel(id=9, name=Java冢狐-4, age=27, salary=40000.0), UserModel(id=10, name=Mybatis-4, age=22, salary=40000.0), UserModel(id=11, name=Java冢狐-5, age=28, salary=50000.0), UserModel(id=12, name=Mybatis-5, age=23, salary=50000.0), UserModel(id=13, name=Java冢狐, age=1, salary=0.0), UserModel(id=14, name=冢狐, age=23, salary=50000.0), UserModel(id=59, name=Java冢狐, age=30, salary=50000.0), UserModel(id=69, name=Java冢狐, age=30, salary=50000.0), UserModel(id=89, name=Java冢狐, age=30, salary=50000.0)] Mapper接口的使用为什么需要Mapper接口上面我们讲解了对一个表的增删改查操作,都是通过调用SqlSession中的方法来完成的,大家再来看一下SqlSession接口中刚才用到的几个方法的定义: int insert(String statement, Object parameter); int update(String statement, Object parameter); int delete(String statement, Object parameter); <E> List<E> selectList(String statement); 这些方法的特点我们来看一下:
这些都是使用过程中不方便的情况,想要方便的使用,就需要用到mybatis中的Mapper接口,我们可以定义一个interface,然后和Mapper xml关联起来,Mapper xml中的操作和Mapper接口中的方法会进行绑定,当我们调用Mapper接口的方法的时候,会间接调用到Mapper xml中的操作,接口的完整类名需要和Mapper xml中的namespace一致。 Mapper接口的用法(三步)步骤1:定义Mapper接口去看一下,user.xml中的namespace,是: <mapper namespace="zhonghu.mybatis.chat01.UserMapper"> 我们创建的接口完整的名称需要和上面的namespace的值一样,下面我们创建一个接口UserMapper,如下: UserMapper.xml中有4个操作,我们需要在UserMapper接口中也定义4个操作,和UserMapper.xml的4个操作对应,如下: package zhonghu.mybatis.chat01; import java.util.List; public interface UserMapper { int insertUser(UserModel model); int updateUser(UserModel model); int deleteUser(Long userId); List<UserModel> getUserList(); } UserMapper接口中定义了4个方法,方法的名称需要和UserMapper.xml具体操作的id值一样,这样调用UserMapper接口中的方法的时候,才会对应的找到UserMapper.xml中具体的操作。 比如调用UserMapper接口中的insertUser方法,mybatis查找的规则是:通过接口完整名称.方法名称去Mapper xml中找到对应的操作。 步骤2:通过SqlSession获取Mapper接口对象SqlSession中有个getMapper方法,可以传入接口的类型,获取具体的Mapper接口对象,如下: /** * Retrieves a mapper. * @param <T> the mapper type * @param type Mapper interface class * @return a mapper bound to this SqlSession */ <T> T getMapper(Class<T> type); 如获取UserMapper接口对象: UserMapper mapper = sqlSession.getMapper(UserMapper.class); 步骤3:调用Mapper接口的方法对db进行操作如调用UserMapper接口的insert操作: @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //创建UserModel对象 UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("Java冢狐").age(30).salary(50000D).build(); //执行插入操作 int insert = mapper.insertUser(userModel); log.info("影响行数:{}", insert); } } 案例:使用Mapper接口来实现增删改查创建一个测试类,代码如下: package zhonghu.mybatis.chat01; import lombok.extern.slf4j.Slf4j; 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 org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; @Slf4j public class UserMapperTest { private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //读取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //构建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } @Test public void insertUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //创建UserModel对象 UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("Java冢狐").age(30).salary(50000D).build(); //执行插入操作 int insert = mapper.insertUser(userModel); log.info("影响行数:{}", insert); } } @Test public void updateUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //创建UserModel对象 UserModel userModel = UserModel.builder().id(1L).name("Java冢狐,你好").age(18).salary(5000D).build(); //执行更新操作 int result = mapper.updateUser(userModel); log.info("影响行数:{}", result); } } @Test public void deleteUser() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //定义需要删除的用户id Long userId = 1L; //执行删除操作 int result = mapper.deleteUser(userId); log.info("影响行数:{}", result); } } @Test public void getUserList() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); //执行查询操作 List<UserModel> userModelList = mapper.getUserList(); userModelList.forEach(item -> { log.info("{}", item); }); } } } 大家认真看一下上面的代码,这次我们使用了UserMapper来间接调用UserMapper.xml中对应的操作,可以去运行一下感受一下效果。 Mapper接口使用时注意的几点
Mapper接口的原理这个使用java中的动态代理实现的,mybatis启动的时候会加载全局配置文件mybatis-config.xml,然后解析这个文件中的mapper元素指定的UserMapper.xml,会根据UserMapper.xml的namespace的值创建这个接口的一个动态代理,具体可以去看一下mybatis的源码,主要使用java中的Proxy实现的,使用java.lang.reflect.Proxy类中的newProxyInstance方法,我们可以创建任意一个接口的一个代理对象: public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 我们使用Proxy来模仿Mapper接口的实现: package zhonghu.mybatis.chat01; import lombok.extern.slf4j.Slf4j; 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 org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; @Slf4j public class UserMapperTest { public static class UserMapperProxy implements InvocationHandler { private SqlSession sqlSession; private Class<?> mapperClass; public UserMapperProxy(SqlSession sqlSession, Class<?> mapperClass) { this.sqlSession = sqlSession; this.mapperClass = mapperClass; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.debug("invoke start"); String statement = mapperClass.getName() + "." + method.getName(); List<Object> result = sqlSession.selectList(statement); log.debug("invoke end"); return result; } } private SqlSessionFactory sqlSessionFactory; @Before public void before() throws IOException { //指定mybatis全局配置文件 String resource = "mybatis-config.xml"; //读取全局配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); //构建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); this.sqlSessionFactory = sqlSessionFactory; } @Test public void test1() { try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) { UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapperTest.class.getClassLoader(), new Class[]{UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class)); log.info("{}", userMapper.getUserList()); } } } 上面代码中:UserMapper是没有实现类的,可以通过Proxy.newProxyInstance给UserMapper接口创建一个代理对象,当调用UserMapper接口的方法的时候,会调用到UserMapperProxy对象的invoke方法。 运行一下test1用例,输出如下: 29:37.847 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Preparing: SELECT * FROM user 29:37.865 [main] DEBUG z.m.chat01.UserMapper.getUserList - ==> Parameters: 29:37.878 [main] DEBUG z.m.chat01.UserMapper.getUserList - <== Total: 16 29:37.878 [main] DEBUG z.mybatis.chat01.UserMapperTest - invoke end 29:37.878 [main] INFO z.mybatis.chat01.UserMapperTest - [UserModel(id=2, name=修改冢狐, age=23, salary=50000.0), UserModel(id=3, name=修改冢狐, age=24, salary=6666.66), UserModel(id=4, name=Mybatis-1, age=19, salary=10000.0), UserModel(id=5, name=Java冢狐-2, age=25, salary=20000.0), UserModel(id=6, name=Mybatis-2, age=20, salary=20000.0), UserModel(id=7, name=Java冢狐-3, age=26, salary=30000.0), UserModel(id=8, name=Mybatis-3, age=21, salary=30000.0), UserModel(id=9, name=Java冢狐-4, age=27, salary=40000.0), UserModel(id=10, name=Mybatis-4, age=22, salary=40000.0), UserModel(id=11, name=Java冢狐-5, age=28, salary=50000.0), UserModel(id=12, name=Mybatis-5, age=23, salary=50000.0), UserModel(id=13, name=Java冢狐, age=1, salary=0.0), UserModel(id=14, name=冢狐, age=23, salary=50000.0), UserModel(id=59, name=Java冢狐, age=30, salary=50000.0), UserModel(id=69, name=Java冢狐, age=30, salary=50000.0), UserModel(id=89, name=Java冢狐, age=30, salary=50000.0)] 注意上面输出的invoke start和invoke end,可以看到我们调用userMapper.getUserList时候,被UserMapperProxy#invoke方法处理了。 Mybatis中创建Mapper接口代理对象使用的是下面这个类,大家可以去研究一下: public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } |
|