MyBatis笔记
【1.在项目中导入Mybatis(Maven项目)】
- 添加Mybatis依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency>
- 添加mysql连接器依赖
<dependency> <groupId>mysql</groupId> <artifactId>myql-connector-java</artifactId> <version>?</version> </dependency>
【2.配置Mybatis】
- MyBatis主配置文件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"> <!-- 文档类型定义(某些框架如果缺少这玩意可能会不工作,比如hibernate) --> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!-- 事务管理器 --> <dataSource type="POOLED"> <!-- 数据源配置 --> <property name="driver" value="com.mysql.jdbc.Driver"/> <!-- 驱动器类名(该类位于myql-connector-java包中,请导入) --> <property name="url" value="jdbc:mysql://localhost:3306/rest"/> <!-- 连接串 --> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <!-- 指定映射文件 --> <mappers> <mapper resource="mapper/user.xml"/> <!-- resource表示映射文件路径 --> </mappers> </configuration>
- MyBatis映射文件user.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 文档类型定义 --> <mapper namespace="UserScope"> <!-- 配置Mapper,一般一个Mapper对应一个实体类 --> <select id="getAll" resultType="com.crframe.rest.entity.User"> <!-- 配置一个statement--> select * from puser <!-- sql语句--> </select> <select id="getById" resultType="com.crframe.rest.entity.User"> select * from puser WHERE id = #{id} <!-- #{...}表示sql参数--> </select> <insert id="add"> INSERT INTO puser (name, password) VALUES (#{name}, #{password}) </insert> </mapper>
- 参数详解
- mapper[namespace] // mapper的名称空间,可为字符串,或全类名
- select[id] // select语句的标识(JAVA可通过namespace+id找到<select>)
- select[result] // SQL查询返回的类型(容器是自动的,亦可手动指定容器为List,Map等)
【3.在JAVA中使用Mybatis】
- 一个简单的查询示例
public class Restpplication { // Mybatis需要用到的一些对象 private static SqlSessionFactoryBuilder builder = null; private static SqlSessionFactory factory = null; private static SqlSession session = null; static { try { builder = new SqlSessionFactoryBuilder(); // 读取配置文件(通过配置文件来生成session工厂) factory = builder.build(Resources.getResourceAsStream("mybatis-config.xml")); session = factory.openSession(); // 获取session,session可进行增删改查操作 } catch (Exception e){ System.out.println("初始化数据库连接时出现了错误:"); e.printStackTrace(); } } public static void main(String[] args) throws IOException { System.out.println("hello"); // selectList是session内置的查询方法,返回List,User是实体类,参数UserScope.getAll表示statement的id(见上面的Mapper) List<User> users = session.selectList("UserScope.getAll"); // 遍历和打印结果 for(User user : users){ System.out.println(user); } }
- 使用小结
与JDBC相比,Mybatis具有以下特点:
· 数据库连接信息、数据表与实体类的关联是可配置的
· sql语句与业务代码分离
· 很少涉及事务操作
· 自动转换查询结果的类型
- 补充:
· 根据测试,数据库的连接出现在SqlSession执行SQL语句前。
· 如果不想做额外的配置,实体类的属性名应该和表的字段名保持一致,类型也得一致
· 设置主配置文件的setting.mapUnderscoreToCamelCase = true来开启驼峰映射,last_login_time => lastLoginTime
【4.使用DAO】
- DAO示例
public interface UserDao { List<User> getAll(); }
可以看出DAO是一个接口,我们不需要实现它,该接口可以由Mybatis自动实现。
使用Dao时,mapper文件需要这样:
<mapper namespace="com.crframe.rest.dao.UserDao">...</mapper>
如上,namespace需要设置为接口名,这样才能确定mapper文件和DAO接口的关联关系
- 使用DAO
public static void main(String[] args) throws IOException { System.out.println("hello"); UserDao dao = session.getMapper(UserDao.class); // getMapper会返回一个UserDao接口的实现类(使用Proxy) List<User> users = dao.getAll(); // 之前的配置工作使得dao.getAll()能正确地匹配上mapper文件中的SQL语句 for(User user : users){ System.out.println(user); } }
- 使用小结
· 使用DAO可以获得更好的代码提示,在实际开发中更常用。
· DAO也可称之为Mapper
【5.使用注解】
注解是映射文件的替代品。注解加在Dao(Mapper)上
- 注解配置
使用注解,主配置文件中的<mapper>需要变成这样
<mapper class="com.crframe.rest.dao.UserDao"/>
可以看出,由原来的映射文件路径变成了类路径,不再需要mapper文件
- 使用注解
public interface UserDao { @Select("Select * from puser") List<User> getAll(); @Select("Select * from puser where id = #{id} limit 0,1") User getById(int id); }
可以看出,SQL语句现在跑到注解里了;使用getMapper获得DAO的实例:
User u = new User(); UserDao dao = session.getMapper(UserDao.class); u = dao.getById(1); System.out.println(u);
备注:
非查询操作时,并发事务可能会导致一些问题,请注意session的提交和关闭。
我们并没有编写DAO的实现,但确实获得DAO的实例,这使用了代理,返回的dao是代理对象,执行该对象的方法时会触发自定义的回调操作。
- 多参数时的注解
public interface IllustDao { @Select("Select * from pillust limit #{start}, #{count}") List<Illust> limit(int start, int count); }
上述查询运行时会出错,原因是JAVA反射机制(默认情况下)无法得知方法参数的名字,也就无法将SQL语句中的参数与方法的参数对应起来
解决:
方法1:
@Select("Select * from pillust limit #{param1}, #{param2}")
方法2:
List<Illust> limit(@Param("start") int start, @Param("count") int count); // 荐
【6.高级查询】
当查询变得复杂时,可能出现以下问题:
① 实体类和数据表的字段名难以保持一致
解决方法:自定义映射
<resultMap id="userMap" type="User" autoMapping="true"> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/> </resultMap> <select id="getAll" resultMap="userMap"> <!-- userMap为<ResultMap>的id属性 --> select * from puser </select>
注:resultMap的autoMapping可以设置自动映射,会自动映射没有设置的其他字段
resultType实际就是开启了autoMapping的resultMap
② 多表查询时复杂结果集的映射比较头疼,比如
Select pillust.*,puser.works as user_works, puser.name as user_name from pillust, puser where pillust.user_id = puser.id and puser.id = 8
解决方法:
- 返回类型设置为Map,这样会失去实体类的优势,值还要手动装箱
- 增加一个实体类以保证正确地映射,主要有两种方式:
- 继承
class IllustDetail extends Illust{ // extra fields }
- 组合
class IllustDetail2 { public Illust illust; // extra fields }
使用组合需要额外做些设置,详见下节
【7.多表查询时的结果映射】
提示:
· Mybatis中,可以同时为一个DAO指定映射文件和注解
· 复杂结果集的映射建议使用映射文件而不是注解
- 一对一查询的映射
描述:
包含的列是两张表组合起来的列(一般是一张表为主,另一张表为次)。
实体文件:
public class IllustDetail2 { private Illust illust; // 主要的列 private String userName; // 额外的列 private Integer userWorks; // 额外的列 }
作品信息包含作者的基本信息
DAO文件:
public interface IllustDao { List<IllustDetail2> allWithUserInfo(); }
映射文件:
<mapper namespace="com.crframe.rest.dao.IllustDao"> <resultMap id="illustDetail" type="com.crframe.rest.entity.IllustDetail2" autoMapping="true"> <association property="illust" javaType="com.crframe.rest.entity.Illust" autoMapping="true"/> <!-- 将主要的列映射到illust属性中 --> </resultMap> <select id="allWithUserInfo" resultMap="illustDetail"> <!-- 多表查询(用户作品中包含用户信息) --> Select pillust.*,puser.works as user_works, puser.name as user_name from pillust left join puser on pillust.user_id = puser.id </select> </mapper>
说明:<association>标签用来指定实体类中的复杂对象的映射方式,该对象一般是其他实体对象。
- 一对多查询的映射
描述:
一个实体对象包含另一个实体对象的集合
实体文件:
public class UserDetail extends User { private List<Illust> illusts; }
用户信息包含用户的所有作品
DAO文件:
public interface UserDao { List<UserDetail> getWithIllusts(int id); }
映射文件:
<mapper namespace="com.crframe.rest.dao.UserDao"> <resultMap id="userDetail" type="com.crframe.rest.entity.UserDetail" autoMapping="true"> <id property="id" column="id"/> <collection property="illusts" ofType="com.crframe.rest.entity.Illust" columnPrefix="pillust"> <id property="id" column="pid"/> <result property="userId" column="uid"/> </collection> </resultMap> <select id="getWithIllusts" resultMap="userDetail"> <!-- 多表查询 --> Select puser.*, pillust.id as pid, pillust.user_id as uid from puser left join pillust on puser.id = pillust.user_id </select> </mapper>
说明:
<collection>标签用来指定实体类中的集合对象的映射方式,该对象一般是其他实体对象。
mysql查询返回列只在重复列名前添加前缀
【8.级联查询】
描述:
对实体类中的某个对象关联一个其他查询。
public class IllustDetail extends Illust implement Serializable { private User user; }
如上,illustDetail中携带了用户信息,要获得用户信息,可以先查出illust,再用其中的user_id查出user,因此user字段需要关联一个【其他查询】
Illust.xml映射文件:
<mapper namespace="com.crframe.rest.dao.IllustDao"> <resultMap id="IllustDetailX" type="com.crframe.rest.entity.IllustDetail" autoMapping="true"> <id property="id" column="id" javaType="int"/> <association property="user" select="com.crframe.rest.dao.UserDao.get" column="user_id" autoMapping="true"/> </resultMap> <select id="allWithUserInfo2" resultMap="IllustDetailX"> Select * from pillust; </select> </mapper>
User.xml映射文件
<mapper namespace="com.crframe.rest.dao.UserDao"> <select id="get" resultType="com.crframe.rest.entity.User"> select * from puser WHERE id = #{id} </select> </mapper>
association标签就是用来为user属性关联一个其他查询,select表示要关联的查询的id(由namespace + select id组成),column表示为该查询提供的传参。