SqlSession相当于一个连接,可以使用这个对象对db执行增删改查操作,操作完毕之后需要关闭,使用步骤:
1.获取SqlSession对象:通过该sqlSessionFactory.openSession方法获取SqlSession对象
2.对db进行操作:使用SqlSession对象进行db操作
3.关闭SqlSession对象:sqlSession.close();
常见的使用方式如下:
//获取SqlSession
SqlSession sqlSession = this.sqlSessionFactory.openSession();
try {
//执行业务操作,如:增删改查
} finally {
//关闭SqlSession
sqlSession.close();
}
上面我们将SqlSession的关闭放在finally块中,确保close()一定会执行。更简单的方式是使用java中的try()的方式,如下:
try (SqlSession sqlSession = this.sqlSessionFactory.openSession();) {
//执行业务操作,如:增删改查
}
需求:传入UserModel对象,然后将这个对象的数据插入到t_user表中。
创建一个UserModel
新建一个com.javacode2018.chat02.UserModel类,代码如下:
package com.javacode2018.chat02;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
private Long id;
private String name;
private Integer age;
private Double salary;
private Integer sex;
}
这个类的字段和t_user表对应。
UserMapper.xml中定义插入操作
我们说过了,对t_user表的所有sql操作,我们都放在UserMapper.xml中,我们在UserMapper.xml中加入下面配置,使用insert元素定义插入操作:
<!-- insert用来定义一个插入操作
id:操作的具体标识
parameterType:指定插入操作接受的参数类型
-->
<insert id="insertUser" parameterType="com.javacode2018.chat02.UserModel">
<![CDATA[
INSERT INTO t_user (id,name,age,salary,sex) VALUES (#{
id},#{
name},#{
age},#{
salary},#{
sex})
]]>
</insert>
insert元素用来定义了一个对db的insert操作
id:是这个操作的一个标识,一会通过mybatis执行操作的时候会通过这个namespace和id引用到这个insert操作,
parameterType:用来指定这个insert操作接受的参数的类型,可以是:各种javabean、map、list、collection类型的java对象,我们这个插入接受的是UserModel对象。
insert元素内部定义了具体的sql,可以看到是一个insert的sql,向t_user表插入数据。
需要插入的值从UserModel对象中获取,取UserModel对象的的字段,使用#{字段}这种格式可以获取到UserModel中字段的值。
调用SqlSession.insert方法执行插入操作
t_user插入的sql我们已经在UserMapper中写好,此时我们怎么调用呢?
需要调用SqlSession.insert方法:
int insert(String statement, Object parameter)
这个方法有2个参数:
statement:表示那个操作,值为Mapper xml的namespace.具体操作的id,如需要调用UserMapper.xml中的insertUser操作,这个值就是:
com.javacode2018.chat02.UserMapper.insertUser
parameter:insert操作的参数,和Mapper xml中的insert中的parameterType指定的类型一致。
返回值为插入的行数。
UserTest类中新增一个测试用例:
@Test
public void insertUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) {
//创建UserModel对象
UserModel userModel = UserModel.builder().id(2L).name("javacode2018").age(30).salary(50000D).sex(1).build();
//执行插入操作
int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
log.info("插入影响行数:{}", result);
//提交事务
sqlSession.commit();
}
}
运行输出如下:
01:46.683 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==> Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?)
01:46.745 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==> Parameters: 2(Long), javacode2018(String), 30(Integer), 50000.0(Double), 1(Integer)
01:46.751 [main] DEBUG c.j.chat02.UserMapper.insertUser - <== Updates: 1
01:46.751 [main] INFO com.javacode2018.chat02.UserTest - 影响行数:1
输出中打印了详细的sql语句,以及sql的参数信息,可以看到Mapper xml中的#{}被替换为了?,这个使用到了jdbc中的PreparedStatement来对参数设置值。
输出中的第二行详细列出了参数的值以及每个值的类型。
第三行输出了insert的结果为1,表示插入成功了1行记录。
去db中看一下,如下,插入成功:
mysql> SELECT * FROM t_user;
+----+---------------+-----+----------+-----+
| id | name | age | salary | sex |
+----+---------------+-----+----------+-----+
| 1 | 路人甲Java | 30 | 50000.00 | 1 |
+----+---------------+-----+----------+-----+
1 row in set (0.00 sec)
上面代码中创建SqlSession,我们使用的是sqlSessionFactory.openSession()创建的,这个方法创建的SqlSession,内部事务是非自动提交的方式,所以需要我们手动提交:
sqlSession.commit();
如果想自动提交事务,可以将上面的测试用例改成下面这样:
@Test
public void insertUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
//创建UserModel对象
UserModel userModel = UserModel.builder().id(1L).name("路人甲Java").age(30).salary(50000D).sex(1).build();
//执行插入操作
int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
log.info("影响行数:{}", result);
}
}
上面在创建SqlSession的时候调用了sqlSessionFactory.openSession(true),指定事务为自动提交模式,所以最后我们不需要手动提交事务了。
需求:传入UserModel对象,然后通过id更新数据。
UserMapper.xml中定义Update操作
使用update定义更新操作:
<!-- update用来定义一个更新操作
id:操作的具体标识
parameterType:指定操作接受的参数类型
-->
<update id="updateUser" parameterType="com.javacode2018.chat02.UserModel">
<![CDATA[
UPDATE t_user SET name = #{
name},age = #{
age},salary = #{
salary},sex = #{
sex} WHERE id = #{
id}
]]>
</update>
写法和insert操作的写法类似,指定id标识、parameterType指定操作的参数类型,元素体中是具体的sql语句。
调用SqlSession.update方法执行更新操作
需要调用SqlSession.update方法:
int update(String statement, Object parameter)
这个方法有2个参数:
statement:表示哪个操作,值为Mapper xml的namespace.具体操作的id,如需要调用UserMapper.xml中的updateUser操作,这个值就是:
com.javacode2018.chat02.UserMapper.updateUser
parameter:update操作的参数,和Mapper xml中的update中的parameterType指定的类型一致。
返回值为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).sex(0).build();
//执行更新操作
int result = sqlSession.update("com.javacode2018.chat02.UserMapper.updateUser", userModel);
log.info("影响行数:{}", result);
}
}
运行输出:
14:09.051 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==> Preparing: UPDATE t_user SET name = ?,age = ?,salary = ?,sex = ? WHERE id = ?
14:09.095 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==> Parameters: 路人甲Java,你好(String), 18(Integer), 5000.0(Double), 0(Integer), 1(Long)
14:09.100 [main] DEBUG c.j.chat02.UserMapper.updateUser - <== Updates: 1
14:09.101 [main] INFO com.javacode2018.chat02.UserTest - 影响行数:1
db中去看一下:
mysql> SELECT * FROM t_user;
+----+------------------------+-----+----------+-----+
| id | name | age | salary | sex |
+----+------------------------+-----+----------+-----+
| 1 | 路人甲Java,你好 | 18 | 5000.00 | 0 |
| 2 | javacode2018 | 30 | 50000.00 | 1 |
+----+------------------------+-----+----------+-----+
2 rows in set (0.00 sec)
需求:根据用户的id删除对应的用户记录
UserMapper.xml中定义Delete操作
使用update元素定义删除操作:
<!-- update用来定义一个删除操作
id:操作的具体标识
parameterType:指定操作接受的参数类型
-->
<update id="deleteUser" parameterType="java.lang.Long">
<![CDATA[
DELETE FROM t_user WHERE id = #{
id}
]]>
</update>
写法和update操作的写法类似,指定id标识、parameterType指定操作的参数类型,用户id为Long类型的,元素体中是具体的delete语句。
调用SqlSession.update方法执行更新操作
需要调用SqlSession.delete方法:
int delete(String statement, Object parameter)
这个方法有2个参数:
statement:表示哪个操作,值为Mapper xml的namespace.具体操作的id,如需要调用UserMapper.xml中的deleteUser操作,这个值就是:
com.javacode2018.chat02.UserMapper
parameter:delete操作的参数,和Mapper xml中的delete中的parameterType指定的类型一致。
返回值为delete影响行数。
UserTest类中新增一个测试用例:
@Test
public void deleteUser() {
try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
//定义需要删除的用户id
Long userId = 1L;
//执行删除操作
int result = sqlSession.delete("com.javacode2018.chat02.UserMapper.deleteUser", userId);
log.info("影响行数:{}", result);
}
}
运行输出:
24:45.427 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==> Preparing: DELETE FROM t_user WHERE id = ?
24:45.476 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==> Parameters: 1(Long)
24:45.485 [main] DEBUG c.j.chat02.UserMapper.deleteUser - <== Updates: 1
24:45.485 [main] INFO com.javacode2018.chat02.UserTest - 影响行数:1
需求:查询所有用户信息
UserMapper.xml中定义Select操作
<!-- select用来定义一个查询操作
id:操作的具体标识
resultType:指定查询结果保存的类型
-->
<select id="getUserList" resultType="com.javacode2018.chat02.UserModel">
<![CDATA[
SELECT * FROM t_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("com.javacode2018.chat02.UserMapper.getUserList");
log.info("结果:{}", userModelList);
}
}
多插入几行,然后运行上面的用例,输出如下:
36:39.015 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
36:39.048 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters:
36:39.066 [main] DEBUG c.j.chat02.UserMapper.getUserList - <== Total: 3
36:39.067 [main] INFO com.javacode2018.chat02.UserTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
36:39.069 [main] INFO com.javacode2018.chat02.UserTest - UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1)
36:39.069 [main] INFO com.javacode2018.chat02.UserTest - UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1)
上面我们讲解了对一个表的增删改查操作,都是通过调用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一致。
去看一下,UserMapper.xml中的namespace,是:
<mapper namespace="com.javacode2018.chat02.UserMapper">
我们创建的接口完整的名称需要和上面的namespace的值一样,下面我们创建一个接口com.javacode2018.chat02.UserMapper,如下:
package com.javacode2018.chat02;
public interface UserMapper {
}
UserMapper.xml中有4个操作,我们需要在UserMapper接口中也定义4个操作,和UserMapper.xml的4个操作对应,如下:
package com.javacode2018.chat02;
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中找到对应的操作。
SqlSession中有个getMapper方法,可以传入接口的类型,获取具体的Mapper接口对象,如下:
/**
* Retrieves a mapper.
* @param 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);
如调用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).sex(1).build();
//执行插入操作
int insert = mapper.insertUser(userModel);
log.info("影响行数:{}", insert);
}
}
案例:使用Mapper接口来实现增删改查
chat02/src/test/java中创建一个测试类,代码如下:
package com.javacode2018.chat02;
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).sex(1).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).sex(0).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 com.javacode2018.chat02;
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 ProxyTest {
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(ProxyTest.class.getClassLoader(), new Class[]{
UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class));
log.info("{}", userMapper.getUserList());
}
}
}
上面代码中:UserMapper是没有实现类的,可以通过Proxy.newProxyInstance给UserMapper接口创建一个代理对象,当调用UserMapper接口的方法的时候,会调用到UserMapperProxy对象的invoke方法。
运行一下test1用例,输出如下:
16:34.288 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke start
16:34.555 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Preparing: SELECT * FROM t_user
16:34.580 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters:
16:34.597 [main] DEBUG c.j.chat02.UserMapper.getUserList - <== Total: 4
16:34.597 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke end
16:34.597 [main] INFO com.javacode2018.chat02.ProxyTest - [UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1), UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575623283897, name=路人甲Java, age=30, salary=50000.0, sex=1)]
注意上面输出的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);
}
}