MyBatis基础(二)
MyBatis接口开发
MyBatis接口开发其实就是:mapper(映射)动态代理方式的crud。
原则:约定优于配置。
硬编码方式
abc.java:
Configuration conf = new Configuration();
conf.setName("myProject") ;
配置方式:
abc.xml:
<name>myProject</name>
约定:默认值就是myProject
具体实现的步骤
-
约定的目标:省略掉statement,即根据约定 直接可以定位出SQL语句
-
基础方式执行SQL
SqlSession session = sessionFacotry.openSession() ; //namespace.id 找到对应的 SQL String statement = "org.lanqiao.entity.studentMapper.queryStudentByStuno"; Student student = session.selectOne(statement,1) ;
-
-
基础环境
-
数据库和实体类
-
mybatis.jar/数据库驱动
jar
包、conf.xml、mapper.xml
-
-
编写接口,要遵循以下约定:
-
namespace
的值 ,就是 接口的全类名( 接口 - mapper.xml 一一对应) -
方法名和mapper.xml(映射)文件中标签的
id
值相同 -
方法的 输入参数 和 mapper.xml文件中标签的
parameterType
类型一致 (如果mapper.xml的标签中没有 parameterType,则说明方法没有输入参数) -
方法的返回值 和 mapper.xml文件中标签的
resultType
类型一致 (无论查询结果是一个 还是多个(student、List<Student>
),在mapper.xml标签中的resultType中只写 一个(Student);如果没有resultType,则说明方法的返回值为void
) -
接口 如:
package org.lanqiao.mapper; import java.util.List; import org.lanqiao.entity.Student; //操作Mybatis的接口 public interface StudentMapper { //根据学号查询学生信息 //public abstract Student queryStudentByStuno(int stuno); Student queryStudentByStuno(int stuno); }
-
Mapper.xml 如:
<!-- namespace:该mapper.xml映射文件的 唯一标识 --> <!-- 根据约定,namespace值 和 接口的全路径一样 --> <mapper namespace="org.lanqiao.mapper.StudentMapper"> <!--parameterType:输入参数的类型,和接口中对应方法的输入参数类型一致 resultType:查询返回结果值的类型,和接口中方法的 返回值类型 或 泛型 一致 --> <select id="queryStudentByStuno" parameterType="int" resultType="org.lanqiao.entity.Student" > select * from student where stuno = #{stuno} </select> </mapper>
-
-
匹配过程(约定过程)分析
-
根据 接口名 找到 对应的
mapper.xml
文件(根据的是namespace = 接口全类名) -
根据 接口的 方法名 找到 mapper.xml文件中的
SQL标签
(方法名 = SQL标签id
值) -
当我们调用接口中的方法时,程序能自动定位到 某一个Mapper.xml文件中的SQL标签
- 习惯:SQL映射文件(mapper.xml)和 接口 通常放在同一个包中 (注意修改conf.xml中加载mapper.xml文件的路径)
-
-
执行具体代码实现(StudentTest.java)
//根据学号查询单个学生 public static void queryStudentByStuno() throws IOException { //Connection - SqlSession操作MyBatis //conf.xml - > reader Reader reader = Resources.getResourceAsReader("conf.xml") ; //reader ->SqlSession //可以通过build的第二参数 指定数据库环境 SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(reader,"development") ; SqlSession session = sessionFacotry.openSession() ; /* 与基础方式的不同之处: 通过session对象获取接口(session.getMapper(接口.class);),再调用该接口中的方法,程序会自动 执行该方法对应的SQL。 */ StudentMapper studentMapper = session.getMapper(StudentMapper.class) ; Student student = studentMapper.queryStudentByStuno(2) ;//接口中的方法->SQL语句 System.out.println(student); session.close(); }
优化
-
将
conf.xml
中的数据库配置信息抽取成配置文件,再进行读取-
properties文件信息如下:
driver=oracle.jdbc.OracleDriver url=jdbc:oracle:thin:@127.0.0.1:1521:ORCL username=scott password=tiger
-
conf.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"> <configuration> <!-- 动态引入数据库配置文件 --> <properties resource="db.properties"/> <!-- 通过environments的default值 和 environment的 id 来指定 MyBatis运行时的数据库环境--> <environments default="development"> <!-- 开发环境(自己的计算机) --> <environment id="development"> <!-- 事务提交方式: JDBC:利用JDBC方式处理事务(commit rollback close) MANAGED:将事务交由 其他组件去托管(spring ,jboss),默认 会关闭连接。 配置默认不关闭连接 <transactionManager type="MANAGED"/> <property name="closeConnection" value="false"/> --> <transactionManager type="JDBC" /> <!-- 数据源类型: UNPOOLED:传统的JDBC模式(每次访问数据库,均需要 打开、关闭等数据库操作,但是 打开、关闭数据库是比较消耗性能的) POOLED:使用数据库连接池 JNDI:从tomcat中获取一个内置的数据库连接池 (数据库连接池-数据源) --> <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="shishi"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 配置数据库信息 --> <property name="driver" value="oracle.jdbc.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:@192.168.1.183:1521:ORCL"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </dataSource> </environment> <mappers> <!-- 加载映射文件 --> <mapper resource="org/lanqiao/mapper/studentMapper.xml"/> </mappers> </configuration>
-
别名
-
在Mapper.xml文件中,进行增删改查的时候,parameterType 和 resultType 的字符串过长,因此可以为其起个别名。
-
单个别名(在conf.xml中设置)
<configuration> <typeAliases> <!-- 单个别名(别名 忽略大小写) --> <typeAlias type="org.lanqiao.entity.Student" alias="student"/> </typeAliases> </configuration>
-
批量别名(在conf.xml中设置)
<configuration> <typeAliases> <!-- 批量定义别名(别名 忽略大小写),以下会自动将该包中的所有类 批量定义别名: 别名就是类名(不带包名的类名,忽略大小写)--> <package name="org.lanqiao.entity"/> </typeAliases> </configuration>
-
全局参数
-
在
conf.xml
文件中配置<configuration> <settings> <setting name="cacheEnabled" value="false" /> <setting name="lazyLoadingEnabled" value="false" /> </settings> </configuration>
增加数据
-
接口方法
//增加 void addStudent(Student student);
-
Mapper.xml
<insert id="addStudent" parameterType="student" > insert into student(stuno,stuname,stuage,graname) values(#{stuNo},#{stuName},#{stuAge},#{graName} ) </insert>
-
StudentTest
Student student = new Student(13,"ww3",23,"s3"); StudentMapper studentMapper = session.getMapper(StudentMapper.class); studentMapper.addStudent(student);
删除数据
-
接口方法
void deleteStudentByStuno(int stuno);
-
Mapper.xml
<delete id="deleteStudentByStuno" parameterType="int">
delete from student where stuno = #{stuno}
</delete>
-
StudentTest
StudentMapper studentMapper = session.getMapper(StudentMapper.class); studentMapper.deleteStudentByStuno(13);
修改数据
-
接口方法
//修改 void updateStudentByStuno(Student student);
-
Mapper.xml
<update id="updateStudentByStuno" parameterType="student" > update student set stuname=#{stuName} ,stuage=#{stuAge},graname=#{graName} where stuno=#{stuNo} </update>
-
StudentTest
//修改的参数 Student student = new Student(); //修改哪个人,where stuno =2 student.setStuNo(2); //修改成什么样子? student.setStuName("ls"); student.setStuAge(24); student.setGraName("s1"); //执行 StudentMapper studentMapper = session.getMapper(StudentMapper.class); studentMapper.updateStudentByStuno(student);
查询多条数据
-
接口方法
//查询全部 List<Student> queryAllStudents();
-
Mapper.xml
<select id="queryAllStudents" resultType="student" > select * from student </select>
-
StudentTest
StudentMapper studentMapper = session.getMapper( StudentMapper.class) ; List<Student> students = studentMapper.queryAllStudents() ;//接口的方法->SQL
类型转换
-
MyBatis自带一些常见的类型处理器
如:javaType(Integer) --> jdbcType(INTEGER),自行查阅。
-
自定义MyBatis类型处理器
-
目标:java类型 – 数据库(jdbc类型)的相互转换
-
示例
实体类Student : boolean stuSex true: 男 false:女 表student:number stuSex 1: 男 0: 女
-
实现步骤
-
创建转换器
-
方式一:实现TypeHandler接口,重写其方法。
public interface TypeHandler<T> { /** * 用于定义在Mybatis设置参数时该如何把Java类型的参数转换为对应的数据库类型 * @param ps 当前的PreparedStatement对象 * @param i 当前参数的位置 * @param parameter 当前参数的Java对象 * @param jdbcType 当前参数的数据库类型 * @throws SQLException */ void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; /** * 用于在Mybatis获取数据结果集时如何把数据库类型转换为对应的Java类型 * @param rs 当前的结果集 * @param columnName 当前的字段名称 * @return 转换后的Java对象 * @throws SQLException */ T getResult(ResultSet rs, String columnName) throws SQLException; /** * 用于在Mybatis通过字段位置获取字段数据时把数据库类型转换为对应的Java类型 * @param rs 当前的结果集 * @param columnIndex 当前字段的位置 * @return 转换后的Java对象 * @throws SQLException */ T getResult(ResultSet rs, int columnIndex) throws SQLException; /** * 用于Mybatis在调用存储过程后把数据库类型的数据转换为对应的Java类型 * @param cs 当前的CallableStatement执行后的CallableStatement * @param columnIndex 当前输出参数的位置 * @return * @throws SQLException */ T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
-
方式二:继承BaseTypeHandler类,重写其方法。(推荐)
//BaseTypeHandler<java类型> public class BooleanAndIntConverter extends BaseTypeHandler<Boolean>{ //java(boolean)-DB(number) /* * ps:PreparedStatement对象 * i:PreparedStatement对象操作参数的位置 * parameter:java类型的值 * jdbcType:jdbc操作的数据库类型 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException { if(parameter) { //1 ps.setInt(i, 1); }else { //0 ps.setInt(i, 0); } } //db(number)->java(boolean) //通过列名(columnName)拿值 @Override public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException { int sexNum = rs.getInt(columnName) ;//rs.getInt("stuno") ; // if(sexNum == 1) // // return true; // else { // return false ; // } return sexNum == 1?true:false ; } //db(number)->java(boolean) //拿第几列(columnIndex)的值 @Override public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException { int sexNum = rs.getInt(columnIndex) ;//rs.getInt(1) return sexNum == 1?true:false ; } //db(number)->java(boolean) //通过存储过程(cs)拿值 @Override public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { int sexNum = cs.getInt(columnIndex) ;//rs.getInt(1) return sexNum == 1?true:false ; } }
-
-
在
conf.xml
中配置转换器<configuration> <!-- 配置转换器 --> <typeHandlers> <!-- 可以配置多个转换器 --> <typeHandler handler="org.lanqiao.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="INTEGER" /> </typeHandlers> </configuration>
编写接口方法(1)(增加)java类型 —> 数据库jdbc类型
//带转换器的增加 void addStudentWithConverter(Student student);
Mapper.xml(1)(增加)java类型 —> 数据库jdbc类型
<!-- 带转换器的增加 --> <insert id="addStudentWithConverter" parameterType="student" > insert into student(stuno,stuname,stuage,graname,stusex) values(#{stuNo},#{stuName},#{stuAge},#{graName} ,#{stuSex ,javaType=boolean ,jdbcType=INTEGER } ) </insert>
StudentTest(1)(增加)java类型 —> 数据库jdbc类型
//增加学生(带转换器) public static void addStudentWithConverter() throws IOException { //Connection - SqlSession操作MyBatis //conf.xml - > reader Reader reader = Resources.getResourceAsReader("conf.xml") ; //reader ->SqlSession //可以通过build的第二参数 指定数据库环境 SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(reader,"development") ; SqlSession session = sessionFacotry.openSession() ; Student student = new Student(63,"ww53",23,"s3"); student.setStuSex(true);//1 StudentMapper studentMapper = session.getMapper(StudentMapper.class); studentMapper.addStudentWithConverter(student); session.commit(); //提交事务 System.out.println("增加成功"); session.close(); //关闭会话(连接) }
-
编写接口方法(2)(查询) 数据库jdbc类型 —> java类型
//带转换器的查询 Student queryStudentByStunoWithConverter(int stuno);
-
Mapper.xml(2)(查询)数据库jdbc类型 —> java类型
<!-- 查询:使用了类型转换器 1如果 类中属性 和表中的字段 类型能够合理识别 (String-varchar2),则可以使用resultType;否则(boolean-number) 使用resultMap 2如果 类中属性名 和表中的字段名能够合理识别 (stuNo -stuno)则可以使用resultType;否则(id-stuno) 使用resultMap --> <select id="queryStudentByStunoWithConverter" parameterType="int" resultMap="studentResult" > select * from student where stuno = #{stuno} </select> <resultMap type="student" id="studentResult"> <!-- 分为主键id 和非主键 result--> <id property="stuNo" column="stuno" /> <result property="stuName" column="stuname" /> <result property="stuAge" column="stuage" /> <result property="graName" column="graname" /> <result property="stuSex" column="stusex" javaType="boolean" jdbcType="INTEGER"/> </resultMap>
resultMap
应用场景
-
resultMap:实体类的属性、数据表的字段: 类型、名字不同时(stuno,id)
- 一般用 resultType
-
StudentTest(2)(查询)数据库jdbc类型 —> java类型
//查询单个学生(使用了转换器) public static void queryStudentByStunoWithConverter() throws IOException { //Connection - SqlSession操作MyBatis //conf.xml -> reader Reader reader = Resources.getResourceAsReader("conf.xml") ; //reader ->SqlSession //可以通过build的第二参数 指定数据库环境 SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(reader,"development") ; SqlSession session = sessionFacotry.openSession() ; StudentMapper studentMapper = session.getMapper(StudentMapper.class) ; Student student = studentMapper.queryStudentByStunoWithConverter(1) ;//接口中的方法->SQL语句 System.out.println(student); session.close(); }
-
-
-
实体类的属性和表中字段名不一致
-
解决方案
- 在Mapper.xml 的
<resultMap>
标签中配置字段的映射关系:
<resultMap type="student" id="studentResult"> <!-- 分为主键id 和非主键 result --> <id property="stuNo" column="stuno" /> <result property="stuName" column="stuname" /> <result property="stuAge" column="stuage" /> <result property="graName" column="graname" /> <result property="stuSex" column="stusex" javaType="boolean" jdbcType="INTEGER"/> </resultMap>
-
注意
jdbcType="INTEGER",Integer要全大写。
- 在Mapper.xml 的
-