javaweb - Mybatis3
Mybatis详解1
由于SqlSessionFactory一般只需要创建一次,因此我们可以创建一个工具类来集中创建SqlSession,这样会更加方便一些:
public class MybatisUtil {
//在类加载时就进行创建
private static SqlSessionFactory sqlSessionFactory;
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(new FileInputStream("mybatis-config.xml"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取一个新的会话
* @param autoCommit 是否开启自动提交(跟JDBC是一样的,如果不自动提交,则会变成事务操作)
* @return SqlSession对象
*/
public static SqlSession getSession(boolean autoCommit){
return sqlSessionFactory.openSession(autoCommit);
}
}
现在我们只需要在main方法中这样写即可查询结果了:
public static void main(String[] args) {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
List<Student> student = sqlSession.selectList("selectStudent");
student.forEach(System.out::println);
}
}
查询操作
XML配置sql操作- 对应
java进行调用
查询操作在XML配置中使用一个select标签进行囊括
假设我们现在需要编写一个根据ID查询用户的操作,首先我们需要指定select操作的id:
<select id="selectUserById">
</select>
接着是我们需要进行查询的参数,这里我们需要根据用户ID查询,那么传入的参数就是一个int类型的参数,参数也可以是字符串类型的,类型名称:
- 如果是基本类型,需要使用
_int这样前面添加下划线。 - 如果是JDK内置的包装类型或是其他类型,可以直接使用其名称,比如
String、int(Integer的缩写)、Long - 如果是自己编写的类型,需要完整的包名+类名才可以。
当然也可以直接不填这个属性,Mybatis会自动判断:
<select id="selectUserById" parameterType="int">
</select>
接下来就是编写我们的SQL语句了,由于这里我们需要通过一个参数来查询,所以需要填入一个占位符,通过使用#{xxx}或是${xxx}来填入我们给定的属性,名称随便:
<select id="selectUserById" parameterType="int">
select * from user where id = #{id}
</select>
实际上Mybatis也是通过PreparedStatement首先进行一次预编译,来有效地防止SQL注入问题,但是如果使用${xxx}就不再是通过预编译,而是直接传值,因此对于常见的一些查询参数,我们一般都使用#{xxx}来进行操作保证安全性。
最后我们查询到结果后,一般都是将其转换为对应的实体类对象,所以说这里我们之间填写之前建好的实体类名称,使用resultType属性来指定:
<select id="selectUserById" parameterType="int" resultType="com.test.User">
select * from user where id = #{id}
</select>
别名
当然,如果你觉得像这样每次都要写一个完整的类名太累了,也可以为它起个别名,我们只需要在Mybatis的配置文件中进行编写即可:
<typeAliases>
<typeAlias type="com.test.User" alias="User"/>
</typeAliases>
也可以直接扫描整个包下的所有实体类,自动起别名,默认情况下别名就是类的名称:
<typeAliases>
<package name="com.test.entity"/>
</typeAliases>
这样,SQL语句映射配置我们就编写好了,接着就是Java这边进行调用了:
//这里我们填写刚刚的id,然后将我们的参数填写到后面
User user = session.selectOne("selectUserById", 1);
System.out.println(user);
hashmap 转换
当然,如果你不需要转换为实体类,Mybatis也为我们提供了多种转换方案,比如转换为一个Map对象:
<select id="selectUserByIdAndAge" resultType="hashmap">
select * from user where id = #{id}
</select>
//使用Map类型变量进行接受,Key为String类型,Value为Object类型
Map<String, Object> user = session.selectOne("selectUserById", 1);
System.out.println(user);
多参数查询
我们可以尝试接着来写一个同时查询ID和年龄的查询操作:
<select id="selectUserByIdAndAge" resultType="hashmap">
select * from user where id = #{id} and age = #{age}
</select>
因为这里需要多个参数,我们可以使用一个Map (Map.of是JAVA9的特性)或是具有同样参数的实体类来传递,显然Map用起来更便捷一些,注意key的名称需要与我们编写的SQL语句中占位符一致:
User user = session.selectOne("selectUserByIdAndAge", Map.of("id", 1, "age", 18));
System.out.println(user);
resultMap
下面这种情况,实体类中定义的属性名称和我们数据库中的名称不一样
这会导致Mybatis自动处理出现问题:
@Data
public class User {
int uid;
String username;
int age;
}
运行后发现,Mybatis虽然可以查询到对应的记录,但是转换的实体类数据并没有被添加上去,这是因为数据库字段名称与类中字段名称不匹配导致的,我们可以手动配一个resultMap来解决这种问题,直接在Mapper中添加:
<select id="selectUserByIdAndAge" resultMap="user">
select * from user where id = #{id} and age = #{age}
</select>
<resultMap id="user" type="com.test.User">
<!-- 因为id为主键,这里也可以使用<id>标签,有助于提高性能 -->
<result column="id" property="uid"/>
<result column="name" property="username"/>
</resultMap>
column对应数据库字段名property对应实体类属性名
这里我们在resultMap标签中配置了一些result标签,每一个result标签都可以配置数据库字段和类属性的对应关系,这样Mybatis就可以按照我们的配置来正确找到对应的位置并赋值了,没有手动配置的字段会按照之前默认的方式进行赋值。
配置完成后,最终只需要将resultType改为resultMap并指定对应id即可,然后就能够正确查询了。
这里有一个RowBounds参数,用于实现分页效果,但是其分页功能是对查询到的数据进行划分,非常鸡肋,这里不进行介绍,了解即可。
查询列表操作
我们再来尝试编写一下查询一个列表,查询列表时,resultType无需设置为list这种类型,而是使用List内部所包含的类型
所以这里还是填写com.test.User类型或是Map类型:
<select id="selectUsers" resultType="com.test.User">
select * from user;
</select>
由于返回的结果是一个列表,这里我们需要使用selectList方法来执行,如果使用之前的selectOne会导致异常:
List<User> user = session.selectList("selectUsers");
System.out.println(user);
我们同样可以进行简单的条件查询,比如我们想要查询所有年龄大于等于18岁的用户:
<select id="selectUsersByAge" resultType="com.test.User">
select * from user where age > #{age};
</select>
注意由于这里是XML配置,其中一些字符被用作标签表示,无法代表其原本的意思,比如小于、大于符号,分别需要使用<和>来进行转义。
List<User> user = session.selectList("selectUsersByAge", 18);
一个比较特殊的选择方法selectMap
可以将查询结果以一个Map的形式表示,只不过这和我们之前说的Map不太一样,它返回的Map是使用我们想要的属性作为Key,然后得到的结果作为Value的Map,它适用于单个数据查询或是多行数据查询:
//最后一个参数为我们希望作为key的属性
Map<String, User> user = session.selectMap("selectUserById", 1, "id");
可以看到这个Map中确实使用的是id作为Key,然后查询得到的实体对象作为Value。
还有一个比较特殊的选择操作是selectCursor
可以得到一个Cursor对象,同样是用于列表查询的,只不过使用起来和我们之前JDBC中的ResultSet比较类似,也是通过迭代器的形式去进行数据的读取,官方解释它主要用于惰性获取数据,提高性能:
public interface Cursor<T> extends Closeable, Iterable<T> { ... }
可以看到它本身是实现了Iterable接口的,表明它可以获取迭代器或是直接使用foreach来遍历:
Cursor<User> cursor = session.selectCursor("selectUsers");
for (User user : cursor) {
System.out.println(user);
}
只不过这种方式在大部分请情况下还是用的比较少,我们主要还是以selectOne和selectList为主。
最后还有一个普通的select方法,它支持我们使用Lambda的形式进行查询结果的处理:
session.select("selectUsers", context -> { //使用ResultHandler来处理结果
System.out.println(context.getResultObject());
});
结果会自动进行遍历并依次执行我们传入的Lambda表达式。
