Mybatis的前身是Batis,他是一个持久层框架,或称为:ORM框架。
注意:所谓的框架就是别人已经写好的,对一些技术进行封装,比如:java框架一般会封装成为JAR;像其他的如JS,CSS等。而用了ORM框架,就不需要像之前操作JDBC数据库那样,一步一步进行编写,通过MyBatis可以快速的操作数据库!
DAO:Data Access Object 数据访问对象
用来对数据进行持久化操作,如将数据存入数据库、硬盘等,可以持久保存
Object Relation Mapping 对象关系映射,指的是java程序和数据库之间的映射关系
JDBC操作数据库的步骤:
在这数据库操作的一系列流程中,有些代码是不变的,有些代码是可变的
DriverClassName,URL,user,password(也称这)
这里的三个Jar包可以通过IDEA直接加载,也可以通过Maven或Gradle来进行加载
mybatis配置文件使用的是.xml文件,可以分为两大类:
主配置文件 ,在一个mybatis工程中有且只有一个,他是用来配置与整个工程配置相关的信息,如环境配置、插件配置、注册mapper文件等!
映射配置文件,在一个mybatis工程中可以有多个mapper文件,每一个mapper文件相当于原来的Dao的实现类。用来配置Dao功能的相关的SQL操作,如SQL语然啦,CRUD ,字段映射等!
注意:创建MyBatis的主配置文件时,如果一段代码需要重复利用的话,可以用模板来创建!
在这里对一个数据库进行设计;
核心配置文件,该文件一般位于src目录下,一般命名为:mybatis-config.xml(这是习惯问题,其他文件名也可以
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
configuration>
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="hello">
<environment id="hello">
<transactionManager type="jdbc">transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/newdb?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
configuration>
@Bean
public Connection connection(){
// TODO: 2021/7/26 创建一个工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = builder.build(resourceAsStream);
// TODO: 2021/7/26 创建了持久化管理器 ,所谓工厂模式,是将配置信息放入工厂,最终将创建一个持久化管理器
SqlSession sqlSession = build.openSession();
Connection connection = sqlSession.getConnection();
return connection;
}
在数据库中有一个表,那么根据ORM,那么在JAVA中就会有一个对应的类,因此,在创建映射文件的时候,首先得在Java中创建一个类;
用来配置DAO的SQL语句,每个数据库表和对应的类之间都有一个映射文件(xml文件)
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="abc">
<insert id="insertUser" parameterType="com.dream.seeker.entity.impl.GradleUser">
insert into customer(name,salary) values (#{name},#{salary})
insert>
mapper>
我们自定义一个映射文件,映射文件名可以自定义,那么如何告诉应用程序我们定义的映射文件呢?
Mybatis中的主配置文件m中有一个和environments平行的标签mapper。示例如下:
<mappers>
<mapper resource="mapper/mybatis-mapper.xml"/>
mappers>
映射文件是和主配置文件一体的文件,自定义好的映射文件(Mapper)需要在主配置文件中进行注册,映射文件有SQL语句,每个SQL语句都有类似于方法名一样的标识,我们在MyBatis中也可以称之为方法名;他还存在方法参数,该参数是Java中的一个数据对象或者普通的数据,这些对象中的属性值和普通数据就是要传入到数据库中的内容。
@Test
public void insertDate() {
gradleUser.setName("hellojava");
gradleUser.setSalary(3000);
// TODO: 2021/7/27:方式一:纯配置文件,没有接口,直接读取Mapper文件中的SQL语句!
sqlSession.insert("abc.insertUser",gradleUser);
// TODO: 2021/7/27 由于在主配置文件中,自动事务提交管理是关闭的,因此,需要手动对事务进行提交
sqlSession.commit();
}
在MyBatis中,如果需要对数据库的操作进行日志输出,则需要加载数据库日志输出依赖,并配置属性文件,具体代码如下:
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
log4j属性文件必须在类路径src下,且文件名必须命名为:log4j.properties,属性文件的具体内容可以设置如下
log4j.rootLogger=INFO,console,dailyFile
# TODO 发布到阿里云记得添加,另外控制台不输出(只输出warn或者error信息)
# log4j.logger.org.mybatis = INFO
log4j.logger.com.imooc.mapper=INFO
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.encoding=UTF-8
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%l] - [%p] %m%n
# 定期滚动日志文件,每天都会生成日志
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.encoding=UTF-8
log4j.appender.dailyFile.Threshold=INFO
# TODO 本地日志地址,正式环境请务必切换为阿里云地址
log4j.appender.dailyFile.File=C:/logs/maven-ssm-alipay/log.log4j
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%l] - [%p] %m%n
log4j.rootLogger=INFO,consoleAppender,logfile,MAIL
log4j.addivity.org.apache=true
#ConsoleAppender,控制台输出
#FileAppender,文件日志输出
#SMTPAppender,发邮件输出日志
#SocketAppender,Socket 日志
#NTEventLogAppender,Window NT 日志
#SyslogAppender,
#JMSAppender,
#AsyncAppender,
#NullAppender
#文件输出:RollingFileAppender
#log4j.rootLogger = INFO,logfile
log4j.appender.logfile = org.apache.log4j.RollingFileAppender
log4j.appender.logfile.Threshold = INFO
# 输出以上的 INFO 信息
log4j.appender.logfile.File = INFO_log.html
#保存 log 文件路径
Log4j 从入门到详解
10
log4j.appender.logfile.Append = true
# 默认为 true,添加到末尾,false 在每次启动时进行覆盖
log4j.appender.logfile.MaxFileSize = 1MB
# 一个 log 文件的大小,超过这个大小就又会生成 1 个日志 # KB ,MB,GB
log4j.appender.logfile.MaxBackupIndex = 3
# 最多保存 3 个文件备份
log4j.appender.logfile.layout = org.apache.log4j.HTMLLayout
# 输出文件的格式
log4j.appender.logfile.layout.LocationInfo = true
#是否显示类名和行数
log4j.appender.logfile.layout.Title
=title:\u63d0\u9192\u60a8\uff1a\u7cfb\u7edf\u53d1\u751f\u4e86\u4e25\u91cd\u9519\u8b
ef
#html 页面的 < title >
############################## SampleLayout ####################################
# log4j.appender.logfile.layout = org.apache.log4j.SampleLayout
############################## PatternLayout ###################################
# log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
# log4j.appender.logfile.layout.ConversionPattern =% d % p [ % c] - % m % n % d
############################## XMLLayout #######################################
# log4j.appender.logfile.layout = org.apache.log4j.XMLLayout
# log4j.appender.logfile.layout.LocationInfo = true #是否显示类名和行数
############################## TTCCLayout ######################################
# log4j.appender.logfile.layout = org.apache.log4j.TTCCLayout
# log4j.appender.logfile.layout.DateFormat = ISO8601
#NULL, RELATIVE, ABSOLUTE, DATE or ISO8601.
# log4j.appender.logfile.layout.TimeZoneID = GMT - 8 : 00
# log4j.appender.logfile.layout.CategoryPrefixing = false ##默认为 true 打印类别名
# log4j.appender.logfile.layout.ContextPrinting = false ##默认为 true 打印上下文信息
# log4j.appender.logfile.layout.ThreadPrinting = false ##默认为 true 打印线程名
# 打印信息如下:
#2007 - 09 - 13 14 : 45 : 39 , 765 [http - 8080 - 1 ] ERROR com.poxool.test.test -
error 成功关闭链接
###############################################################################
#每天文件的输出:DailyRollingFileAppender
#log4j.rootLogger = INFO,errorlogfile
log4j.appender.errorlogfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorlogfile.Threshold = ERROR
log4j.appender.errorlogfile.File = ../logs/ERROR_log
log4j.appender.errorlogfile.Append = true
#默认为 true,添加到末尾,false 在每次启动时进行覆盖
log4j.appender.errorlogfile.ImmediateFlush = true
#直接输出,不进行缓存
# ' . ' yyyy - MM: 每个月更新一个 log 日志
# ' . ' yyyy - ww: 每个星期更新一个 log 日志
# ' . ' yyyy - MM - dd: 每天更新一个 log 日志
# ' . ' yyyy - MM - dd - a: 每天的午夜和正午更新一个 log 日志
# ' . ' yyyy - MM - dd - HH: 每小时更新一个 log 日志
# ' . ' yyyy - MM - dd - HH - mm: 每分钟更新一个 log 日志
Log4j 从入门到详解
11
log4j.appender.errorlogfile.DatePattern = ' . ' yyyy - MM - dd ' .log '
#文件名称的格式
log4j.appender.errorlogfile.layout = org.apache.log4j.PatternLayout
log4j.appender.errorlogfile.layout.ConversionPattern =%d %p [ %c] - %m %n %d
#控制台输出:
#log4j.rootLogger = INFO,consoleAppender
log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.Threshold = ERROR
log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern =%d %-5p %m %n
log4j.appender.consoleAppender.ImmediateFlush = true
# 直接输出,不进行缓存
log4j.appender.consoleAppender.Target = System.err
# 默认是 System.out 方式输出
#发送邮件:SMTPAppender
#log4j.rootLogger = INFO,MAIL
log4j.appender.MAIL = org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold = INFO
log4j.appender.MAIL.BufferSize = 10
log4j.appender.MAIL.From = [email protected]
log4j.appender.MAIL.SMTPHost = smtp.gmail.com
log4j.appender.MAIL.Subject = Log4J Message
log4j.appender.MAIL.To = [email protected]
log4j.appender.MAIL.layout = org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern =%d - %c -%-4r [%t] %-5p %c %x - %m %n
#数据库:JDBCAppender
log4j.appender.DATABASE = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL = jdbc:oracle:thin:@ 210.51 . 173.94 : 1521 :YDB
log4j.appender.DATABASE.driver = oracle.jdbc.driver.OracleDriver
log4j.appender.DATABASE.user = ydbuser
log4j.appender.DATABASE.password = ydbuser
log4j.appender.DATABASE.sql = INSERT INTO A1 (TITLE3) VALUES ( ' %d - %c %-5p %c %x - %m%n
' )
log4j.appender.DATABASE.layout = org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern =% d - % c -%- 4r [ % t] %- 5p % c %
x - % m % n
#数据库的链接会有问题,可以重写 org.apache.log4j.jdbc.JDBCAppender 的 getConnection() 使用数
据库链接池去得链接,可以避免 insert 一条就链接一次数据库
通过IDEA可以连接MySQL数据库,连接数据库后,在编写mapper文件的时候可以有提示信息,可以防止错误的发生。在IDEA连接数据库时,一定要注意设置serverTimezone=UTC,才能正确的连接
File->Setting->Languages&Frameworks->SQL Dialects 将全局和项目的SQL Dialects都设置为:MySQL
基于接口的映射文件,他和纯配置文件的主要区别在于:
具体案例代码:
package com.dream.seeker.dao;
import com.dream.seeker.entity.impl.GradleUser;
public interface InsertUser {
public void insertUser(GradleUser user);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.seeker.dao.InsertUser">
<insert id="insertUser" parameterType="com.dream.seeker.entity.impl.GradleUser">
insert into
gradle(name, salary) values (#{name},#{salary})
insert>
mapper>
@Test
public void test97() {
gradleUser.setName("hello");
gradleUser.setSalary(200);
// TODO: 2021/7/27 通过getMapper()方法,在方法中传入接口的结构信息,可以动态创建一个代理类对象
InsertUser mapper = sqlSession.getMapper(InsertUser.class);
// TODO: 2021/7/27 通过动态生成的代理类对象调用其中方法来驱动映射配置文件中的SQL语句,或者可以说:SQL语句已经动态生成到接口实现类中的方法当中
mapper.insertUser(gradleUser);
// TODO: 2021/7/27 由于在MyBatis主配置文件中,事务管理属性JDBC,因此,需要手动提交事务
sqlSession.commit();
}
知识点:通过接口 + 配置文件 的方式来操作数据库,他基本原理是:通过接口的结构信息 + 配置文件中与接口方法名相同的SQL语句,动态生成(由字节码操作工具)一个接口的实现类及实现类对象,通过这个实现类对象(代理类对象)调用对象中的方法来执行SQL语句,从而操作数据库。其主要优点是:接口动态生成的实例对象每一个方法都对应映射文件中的一个SQL语句!
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = builder.build(resourceAsStream);
// TODO: 2021/7/26 创建了持久化管理器 ,所谓工厂模式,是将配置信息放入工厂,最终将创建一个持久化管理器
SqlSession sqlSession = build.openSession();
因此,在这种情况下,我们可以创建一个工具类,返回一个持久化管理器SqlSession ,同时,需要注意的是:在工具类中,SqlSession对象必须是单例唯一的。具体示例代码如下:
public class GradleMyBatisUtils {
private static SqlSessionFactory factory;
// TODO: 2021/7/28 可以把ThreadLocal<>看成是一个容器,这个容器有判断hash值的方法,确定在里面的实例是否是唯一(即单例)
private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
static {
try {
factory = new SqlSessionFactoryBuilder().build(
GradleMyBatisUtils.class.getClassLoader().getResourceAsStream("classpath:mybatis-config.xml")
);
} catch (Exception e) {
throw new ExceptionInInitializerError("MyBatis初始化失败" + e.getMessage());
}
}
// TODO: 2021/7/28 获取一个持久化管理器,管理器里包数据库连接池,事务和数据库连接的相关信息,具体内容和配置文件中有关
public static SqlSession getSession(){
// TODO: 2021/7/28 从容器中获得对象, 由于是单例的,所以,会依据类型进行匹配
SqlSession sqlSession = local.get();
if (sqlSession == null) {
sqlSession = factory.openSession();
local.set(sqlSession);
}
return sqlSession;
}
// TODO: 2021/7/28 关闭持久化管理对象,关闭此对象,会同时关闭数据库连接池中相关对象,如connection,statement等!
public static void close(){
SqlSession sqlSession = local.get();
if (sqlSession!=null) {
sqlSession.close();
local.remove();
}
}
}
新增知识点:在工具类中,我们可以用ThreadLocal类实例来管理工具类中对象是否为单例,ThreadLocal实例具有存储和管理工具类中指定对象类型的功能,可以用来判断工具类中主对象是否为空,如果不为空的话,可以通过get()方法直接获得相应的对象。
@Test
public void test110() {
SqlSession sqlSession = null;
try {
// TODO: 2021/7/28 从工具类中获得持欠化管理器
sqlSession = MyBatisUtils.getSession();
// TODO: 2021/7/28 下面几段代码是具体对数据库进行操作的代码
GradleUser gradleUser = new GradleUser();
gradleUser.setName("hellojava");
gradleUser.setSalary(5000);
InsertUser mapper = sqlSession.getMapper(InsertUser.class);
mapper.insertUser(gradleUser);
// TODO: 2021/7/28 由于事务采取的方式是JDBC方式,他自动提交是关闭的,因此,需要手动关闭
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
// TODO: 2021/7/28 如果发生异常,事务没有执行完,地么需要回滚操作
sqlSession.rollback();
} finally {
MyBatisUtils.close();
}
}
在主配置文件中,添加标签选项:typeAliases,可以为java类对象设置别名(这个JAVA类对象是存储数据库信息的Bean,使用此类对象设置数据,再将此类对象中的数据传入到数据库),这个java类对象是执行SQL方法中的参数,这个参数他位于MyBatis映射文件中。设置别名主要是在MyBatis主配置文件中,使用标签typeAliases,具体示例如下:
<typeAliases>
<package name="com.dream.seeker.entity.impl"/>
typeAliases>
设置好别名后,那么就可以在MyBatis映射文件中使用别名了,下面的代码是基于接口 + 配置文件的方式定义的mapper(映射)文件,其中namesapce是接口名,id指的是接口的抽象方法名,而parameterType这个地方通过在主配置文件中定义的别名,可以简化许多代码。
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dream.seeker.dao.InsertUser">
<insert id="insertUser" parameterType="GradleUser">
insert into
gradle(name, salary) values (#{name},#{salary})
insert>
mapper>
在属性文件中写连接信息,首先得定义一个属性文件xxxx.properties,将相关的连接信息写入到里面,案例如下:
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/newdb?serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=123456
在定义完属性文件后,需要在MyBatis的主配置文件中将自定义的属性文件添加到主配置文件中,在主配置文件中,用到了标签:properties,具体代码案例如下:
<properties resource="dbcp.properties"/>
在MyBatis主配置文件中定义好了属性文件后,则可以改写主配置文件中的连接信息,改写的内容采用 表 达 式 , 连 接 信 息 也 是 通 过 属 性 后 置 处 理 器 的 结 {}表达式,连接信息也是通过属性后置处理器的结 表达式,连接信息也是通过属性后置处理器的结{}表达式进行转换,具体代码案例如下:
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
dataSource>
基于接口 + 配置文件 的方式对数据库进行插入操作SQL时,一般情况下是不返回主键的,如果需要返回主键,那么需要在mapper文件中添加属性:userGeneratedKey。此属性的作用是保留进行插入SQL操作时的主键,此主键的值保存在哪个位置呢?答案是:保存到方法参数对象的属性中,然而,自定义的参数对象有许多属性,他又会保存到哪个属性当中呢,这时,在mapper文件中,又需要用到一个属性名为KeyProertyr的属性,此属性指的是自定义参数对象中哪个属性。具体代码案例流程如下:
@Component(value = "gradleuser")
public class GradleUser {
private int id;
private String name;
private long salary;
...
}
public interface InsertUser {
public void insertUser(GradleUser user);
public void insertGradle(GradleUser user);
}
<insert id="insertGradle" parameterType="GradleUser" useGeneratedKeys="true" keyProperty="id">
insert into gradle(name, salary) VALUES (#{name},#{salary})
insert>
@Test
public void test130() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtils.getSession();
GradleUser gradleUser = new GradleUser();
gradleUser.setName("thisisjava");
gradleUser.setSalary(3500);
InsertUser mapper = sqlSession.getMapper(InsertUser.class);
mapper.insertGradle(gradleUser);
System.out.println(gradleUser.getId());
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
MyBatisUtils.close();
}
}
在这时统一学习的是基于接口+ 配置文件 的方式
具体代码示例如下:
public interface BatisOne {
void insertStudent(Student student);
void updataStudent(Student student);
void deleteStudent(int id);
}
<update id="updataStudent" parameterType="Student">
update student set name=#{name},age = ${age} where id=#{id}
update>
@Test
public void test46() {
Student student = new Student(1, "hellojava", 30);
batisOne.updataStudent(student);
}
通过Mybatis以数据库表中的数据进行删除时,参数中可以不指定对象,可以直接指定数据库表中的一个数据类型做为参数,也就是我们常说的基本数据类型,如下面,Integer类型做为参数:
public interface BatisOne {
void insertStudent(Student student);
void updataStudent(Student student);
void deleteStudent(int id);
}
<delete id="deleteStudent" parameterType="Integer">
delete from student where id = #{id}
delete>
基本数据类型做为参数时,在映身文件中的SQL语句中,#{XXX}中可以直接用接口方法 中定义的形参标识符来填充XXX点位符。
@Test
public void test52() {
batisOne.deleteStudent(1); //这个地方直接传入基本数据类型
}
在mybatis映射文件中有一个标签为:select,可以对数据库中的数据进行查询操作
<--前提条件:查询结果的字段名必须和对象的属性名相同 -->
<select id="selectStudent" parameterType="Integer" resultType="Student">
select id,name,age from student where id=#{id}
select>
返回的是一个对象,会自动进行映射,当然,查询后的结果集字段名必须和对象中的属性名相相同
对于返回值为一个集合时,这个集合是一个容纳Student对象的集合,那么在映射文件的参数也可以是Student对象,因为Mybatis帮我们封装了许多技术,可以逐条的将数据库表中的每条记录映射到Student对象中,再将对象放入集合中。
具体代码示例如下:
public interface BatisOne {
void insertStudent(Student student);
void updataStudent(Student student);
void deleteStudent(int id);
Student selectStudent(int id);
List<Student> selectAll();
}
<select id="selectStudent" parameterType="Integer" resultType="Student">
select id,name,age from student where id=#{id}
select>
<select id="selectAll" resultType="Student">
select <include refid="studentFiled">include> from student
select>
<sql id="studentFiled">
id,name,age
sql>
<select id="selectAll" resultType="Student">
select <include refid="studentFiled">include> from student
select>
整体映射文件代码如下:包含了更新,删除、查询的所有语句
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dream.dao.batis.BatisOne">
<sql id="studentFiled">
id,name,age
sql>
<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="id">
insert into student(name, age) values (#{name},#{age})
insert>
<update id="updataStudent" parameterType="Student">
update student set name=#{name},age = ${age} where id=#{id}
update>
<delete id="deleteStudent" parameterType="Integer">
delete from student where id = #{id}
delete>
<select id="selectStudent" parameterType="Integer" resultType="Student">
select id,name,age from student where id=#{id}
select>
<select id="selectAll" resultType="Student">
select <include refid="studentFiled">include> from student
select>
mapper>
自动映射:指的是查询结果集的字段名和自定义对象的属性名相同
手动映射:在查询结果集和自定义对象属性名不相同
如何进行手动映射:
在学习手动映射之前,我们需要定义一个自定义对象,这个对象的属性和数据库表中的字段名不一样,如下:
public class NewStudent {
private int user_id;
private String user_name;
private int user_age;
...
}
<select id="selectNewStudent" parameterType="Integer" resultType="NewStudent">
select id user_id, name user_name, age user_age
from student
where id = #{id}
select>
<resultMap id="studentMapper" type="NewStudent">
<id property="user_id" column="id"/>
<result property="user_name" column="name"/>
<result property="user_age" column="age"/>
resultMap>
定义好resultMap,就可以在映射文件中的SQL语句中使用定义的resultMap了
<select id="selectTwoStudent" parameterType="Integer" resultMap="studentMapper">
select id, name, age
from student
where id = #{id}
select>
基于接口和映射配置文件定义的Mybatis中,如果接口定义中的方法定义了两个参数,应该如何处理呢?
重点:学习这点,可以认为把映射文件中的SQL语句都移到了接口的实现类中,这样,更容易理解 SQL语句对形参的调用。
有多个参数,那么在SQL中可以就不写了,在映射文件的SQL不写参数还可以用于Map集合做为接口方法中的参数。
**第一种方式:**在接口中定义的参数,实际上有索引的,第一个参数为0,第二个参数为1。通过实验,这种情况一般用不了,可以在sql语句的点位符中直接引有形参名。
// TODO: 2021/8/11 这里使用索引的方式,一般情况下,索引无效,可以直接使用参数名放在占位符中
ClassStudent selectStudent(String dname, int age);
<select id="selectStudent" resultType="ClassStudent">
select name,age,address from dbuser where name=#{dname} and age=#{age}
select>
**第二种方式:**使用@Param注解,他是标注在接口方法的参数前,注解中定义的名称,也是在映射文件的SQL语句点位符所要用到的名称。
// TODO: 2021/8/11 这里使用注解的方式 ,由于是多参,所以在SQL映射文件中无需定义参数
ClassStudent selectTwoStudent(@Param(value = "helloname") String username, @Param(value = "helloage") int userage);
<select id="selectTwoStudent" resultType="ClassStudent">
select name,age,address from dbuser where name=#{helloname} and age=#{helloage}
select>
**第三种方式:**可以把多个参数变成一个参数,也就是参数封装成一个对象。一般常用的方法,传递进去的对象和返回的对象是同一个对象。如果要传递进去的参数在已知的定义的对象中没有相应的属性,那么可以再自定义一个封装类。
// TODO: 2021/8/11 通过自定义对象,如果自定义对象中有能传递给语句的属性,而且返值也能放到这个对象中,那么参数和返回值可以是同一个类
ClassStudent selectClassStudent(ClassStudent student);
<select id="selectClassStudent" parameterType="ClassStudent" resultType="ClassStudent">
select name,age,address from dbuser where age=#{age}
select>
**第四种方式:**直接将参数封装成一个Map集合。在#{}占位符中,可以直接使用Map集合中Key的值。
// TODO: 2021/8/11 在映射文件中的SQL语句中,无需定义参数,点位符使用Map中的Key值
ClassStudent selectMapStudent(Map map);
<select id="selectMapStudent" resultType="ClassStudent">
select name,age,address from dbuser where age=#{age}
select>
**总结分析:**在一般情况下,使用注解,和自下定义对象的方法比较居多,具体情况还得根据情况而定。
模糊查询主要有二种:一种是手动模糊查询;另一种是使用Bind进行查询
使用手动模糊查询,模糊查询的匹配符是在外面进行定义的,映射文件里厕所SQL语句接收的参数仍是接口中定义的方法形参数标识名
List<Student> selectOut(String s);
<select id="selectOut" resultType="Student">
select name,age from student where name like #{s}
select>
@Test
public void test83() {
String name = "a";
List<Student> students = batisOne.selectOut("%"+name+ "%");
for (Student student : students) {
System.out.println(student);
}
}
使用Bind进行模糊查询,也可以理解成为内置模糊查询,在方法外部不定义匹配标识符,而是在方法里面进行定义
List<Student> selectIn(String s);
<select id="selectIn" resultType="Student">
select name,age from student
<bind name="abc" value="'%'+_paramater+'%'"/>
where name like #{abc}
select>
@Test
public void test94() {
String name = "c";
List<Student> students = batisOne.selectIn(name);
for (Student student : students) {
System.out.println(student);
}
}
根据条件的不同,动态的拼接SQL语句,称之为动态SQL。
动态SQL都是在在映射文件中利用相对应的标签进行定义的,其主要目的就是给指定要查询的语句进动态的拼接
SQL的if语句,其主用途是对传入的参数对象的某个属性进条件判断,如果条件为真,就可以执行或者拼接SQL语句。
示例:对一个对象参数的属性进行条件判断,如果符合相应的条件,则进行SQL语句的拼接。
<select id="selectif" parameterType="Student" resultType="Student">
select name, age
from student
where 1 = 1
<if test="name!=null and name!=''">
and name = #{name}
if>
select>
注意点:判断条件中的属性都来源于方法中的参数,如果接口方法参数是一个自定义对象,那么就来源于这个自定义对象的属性。
在映射文件中的SQL语句中,还有一个标签choose,用于条件的选择,其主要意思是:只会拼接一个SQL语句。
<select id="selectchoose" parameterType="Student" resultType="Student">
select name, age
from student
where
<choose>
<when test="name != null and name !=''">
name=#{name}
when>
<when test="age!=null and age!=0">
age=#{age}
when>
<otherwise>
1=1
otherwise>
choose>
select>
where标签:一般要结合if或choose一起使用,主要有两个作用:
<select id="selectwhere" parameterType="Student" resultType="Student">
select name,age from student
<where>
<if test="name != null and name != ''">
and name=#{name}
if>
where>
select>
更新,修改的时候用set标签,set标签不能单独使用,一般也需要结合if和choose来使用。具体代码示例如下:
<update id="updateset" parameterType="Student">
update student
<set>
<if test="name !=null and name !=''">
name = #{name},
if>
<if test="age!=null and age!=0">
age=#{age}
if>
set>
<where>
<if test="id!=null and id!=''">
and id=#{id}
if>
where>
update>
trim标签的作用,可以在语句的两边去除、增加相关的内容,其主要作用如下:
<select id="selecttrim" parameterType="Student" resultType="Student">
select name,age from student
<trim prefix="where" prefixOverrides="and|or">
<if test="id != null">
and id=#{id}
if>
trim>
select>
增强for循环,如在数据库表中,需要同时找出字段不同值的内容。foreach标签中有多个属性:
其主要代码示例如下:
List<Student> selectforeach(List<Integer> list);
<select id="selectforeach" resultType="Student">
select name,age from student
where id in
<foreach collection="list" item="abc" open="(" close=")" separator=",">
#{abc}
foreach>
select>
@Test
public void test153() {
List<Integer> integers = Arrays.asList(3, 4);
List<dream.entity.impl.Student> selectforeach = batisOne.selectforeach(integers);
for (dream.entity.impl.Student student : selectforeach) {
System.out.println(student);
}
}
多表映射关系核心思想
具体代码录例如下:
映入文件中的SQL语句
<insert id="insertEmp" parameterType="Emp">
insert into emp(empname, empage, deptid)
VALUES (#{empname}, #{empage}, #{dept.deptid})
insert>
测试
@Test
public void test237() {
Dept dept = new Dept();
Dept dept1 = new Dept();
dept.setDeptname("市场部");
dept1.setDeptname("营销部");
Emp emp1 = new Emp("admin", 20, dept);
Emp emp2 = new Emp("javachello", 30, dept1);
try {
dreamDept.insertDept(dept);
dreamDept.insertDept(dept1);
dreamEmp.insertEmp(emp1);
dreamEmp.insertEmp(emp2);
MyBatisUtils.commit();
} catch (Exception e) {
MyBatisUtils.rollback();
e.printStackTrace();
} finally {
MyBatisUtils.close();
}
}
通过以上两段代码,基本上可以确定在ORM中,在多表中,一张表的对象模型,可以包含另外一张表的对象模型。
本节的学习,是 员工表emp为例,在java中,对应的有Emp员工对象,基于接口+ 映射文件,总共四个方面的内容(表、接品、映射配置文件、表对象)
在多对一关系中,一个员工对应有一个部门,一个部门在ORM中也是一个对象,所以,在emp对象中,部门dept就是作为员工对象国的一个属性。在本节中,主要解决的问题是:**如何通把查询出来的内容封装到一个对象中的对象属性。**在这里,主要有两种方式:
这种方式比较容易直观理解,其中心思想是:从数据库表获得的字段数据,根据手动映射关系直接传递到对象当中。
<resultMap id="empMapper" type="Emp">
<id property="empid" column="empid"/>
<result property="empname" column="empname"/>
<result property="empage" column="empage"/>
<association property="dept" javaType="Dept">
<id property="deptid" column="deptId"/>
<result property="deptname" column="deptName"/>
association>
resultMap>
嵌套使用手动映射也有两种情况:
也就是单独把对部门映像结分离出来,其主核心思想是,在配置手动映射的时候,如果一个属性是对象,那么可以针对这个对象定义一个手动配置文件(这个手动映射配置文件可以定义在同一个文件中,也可以定义在另外ORM中与数据库表相关联的映射配置文件中),在查询的映射文件中再通过association标签标签resultMap来使用再次定义的手动配置。
<resultMap id="deptmapperthis" type="Dept">
<id property="deptid" column="deptId"/>
<result property="deptname" column="deptName"/>
resultMap>
引用:
<resultMap id="empMapper2" type="Emp">
<id property="empid" column="empid"/>
<result property="empname" column="empname"/>
<result property="empage" column="empage"/>
<association property="dept" javaType="Dept" resultMap="deptmapperthis"/>
resultMap>
引用:此时的引用是不同与上面的
<resultMap id="empMapper2" type="Emp">
<id property="empid" column="empid"/>
<result property="empname" column="empname"/>
<result property="empage" column="empage"/>
<association property="dept" javaType="Dept" resultMap="dream.dao.batis.DreamDept.deptmapper"/>
resultMap>
此地方需注意的是:查询语句必须包含能传递全手动映射代码所必须的字段值
第三种方式:通过查询过得的字段值传递给另外一张表进行查询
在association标签中,还有一个属性:select,他的主要作用是调用其他映射文件的查询语句,其中还有一个属性:column,这个属性的作用是在调用其他映射文件查询语句时,做为一个参数传递过去的。他做为一个参数传递给查询语句时,如果只有一个参数,查询语句接收参数时,可以任意命名。
<select id="selectAll" resultMap="empMapper3">
select empid, empname, empage, deptid
from emp
select>
<resultMap id="empMapper3" type="Emp">
<id property="empid" column="empid"/>
<result property="empname" column="empname"/>
<result property="empage" column="empage"/>
<association property="dept" javaType="Dept" select="dream.dao.batis.DreamDept.selectDept" column="deptid"/>
resultMap>
做为这三种方式,在实际项目中,我们用哪一种呢?
第三种方式是存在缺陷的,他会有多次查询。主要存在以下几种情况
在一个对象中,定义另外一个对象集合,在java中可以这样理解,而在数据库表中,一对多可以理解为:通过Group BY 分组后的一个字段,在别的字段中包含有多条记录。
通过本节视频学习总结:一多对,在ORM模型中,一个对象中有一个集合属性,这个集合属性存储的是从另外一张表中获取的数据生成的对象。
一对多的操作也有三种方式,和多对一的方式是一样的
<resultMap id="deptnewmapper" type="Dept">
<id property="deptid" column="deptid"/>
<result property="deptname" column="deptname"/>
<collection property="emps" ofType="Emp">
<id property="empid" column="empId"/>
<result property="empname" column="empName"/>
<result property="empage" column="empAge"/>
collection>
resultMap>
嵌套使用手动映射代码,也有两种情况,一是直接使用同一文件中的映射代码,另一个是使用外部文件的映射代码
<sql id="newdeptfiled">
d.deptid,
d.deptname,
e.empid 'empId',
e.empname 'empName',
e.empage 'empAge'
sql>
<resultMap id="deptnewmapper2" type="Dept">
<id property="deptid" column="deptid"/>
<result property="deptname" column="deptname"/>
<collection property="emps" ofType="Emp" resultMap="otherdeptmapper"/>
resultMap>
<resultMap id="otherdeptmapper" type="Emp">
<id property="empid" column="empId"/>
<result property="empname" column="empName"/>
<result property="empage" column="empAge"/>
resultMap>
<select id="selectAlldept" resultMap="deptnewmapper2">
select <include refid="newdeptfiled"/> from dept d left join emp e on d.deptid = e.deptid
select>
其中心思想是:从一张表中得到字段数据,从其中的一个字段数据值做为另一个查询语句的参数,对第二张表进行查询,第二张表查询出来的字段值存储到ORM模型中的对象中,并将其对象传递给一对多对象的集合属性。
具体代码示例如下:
<select id="empselect" parameterType="Integer" resultType="Emp">
select empid, empname, empage from emp where deptid = #{id}
select>
<resultMap id="one" type="Dept">
<id property="deptid" column="deptid"/>
<result property="deptname" column="deptname"/>
<collection property="emps" ofType="Emp" select="empselect" column="deptid"/>
resultMap>
<select id="otherselectdept" resultMap="one">
select deptid, deptname from dept
select>
注意:从字段获得数据值传递给第二个查询语句,这个查询语句可以在同一个文件中,也可以在另外一个文件中,只是引用的方式不同,引用外部文件的查询语句时,使用全类名路径文件名 + 查询语句的ID号。
mybatis-generator是一款Mybatis插件,能够自动生成Mybatis相关的代码,如:
当然,也在存缺点:
该插件并不是在开发过程中使用的,而是开发前做的准备工作
在开发之前通过该插件生成相关的代码,然后将生成的代码拷贝到工程中
使用该插件时,需要添加插件的jar包,但在开发的时候不需要添加,他主要有两个jar包
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.4.0version>
<configuration>
<overwrite>trueoverwrite>
<verbose>trueverbose>
configuration>
plugin>
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="Mysql" targetRuntime="MyBatis3Simple">
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
plugin>
<commentGenerator >
commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://192.168.9.107:3306/jack?useUnicode=true&characterEncoding=UTF8&useSSL=true"
userId="root"
password="root">
<property name="useInformationSchema" value="true"/>
jdbcConnection>
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
javaTypeResolver>
<javaModelGenerator targetPackage="${targetModelPackage}" targetProject="${targetJavaProject}">
javaModelGenerator>
<sqlMapGenerator targetPackage="${targetXMLPackage}" targetProject="${targetResourcesProject}">
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="${targetMapperPackage}" targetProject="${targetJavaProject}">
javaClientGenerator>
<table tableName="student" domainObjectName="Student" />
context>
generatorConfiguration>
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="dbcp-config.properties"/>
<classPathEntry location="${jdbc.jar}"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="false"/>
commentGenerator>
<jdbcConnection
driverClass="${driverClassName}"
connectionURL="${url}"
userId="${username}"
password="${password}">
jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
javaTypeResolver>
<javaModelGenerator targetPackage="com.dream.seeker" targetProject="./src/main/java">
<property name="enableSubPackages" value="false"/>
<property name="constructorBased" value="true"/>
<property name="trimStrings" value="true"/>
<property name="immutable" value="false"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="mappers" targetProject="./src/main/resources">
<property name="enableSubPackages" value="false"/>
sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.*.dao" targetProject="./src/main/java">
<property name="enableSubPackages" value="false"/>
javaClientGenerator>
<table tableName="admin" domainObjectName="Admin" enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
<property name="rootInterface" value="com.*.dao.AdminExtMapper"/>
table>
context>
generatorConfiguration>
DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<properties resource="dbcp-config.properties"/>
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
<context id="mysql" defaultModelType="hierarchical" targetRuntime="MyBatis3Simple" >
<property name="autoDelimitKeywords" value="false"/>
<property name="javaFileEncoding" value="UTF-8"/>
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql:///pss" userId="root" password="admin">
jdbcConnection>
<javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
<property name="forceBigDecimals" value="false"/>
javaTypeResolver>
<javaModelGenerator targetPackage="com._520it.mybatis.domain" targetProject="src/main/java">
<property name="constructorBased" value="false"/>
<property name="enableSubPackages" value="true"/>
<property name="immutable" value="false"/>
<property name="rootClass" value="com._520it.mybatis.domain.BaseDomain"/>
<property name="trimStrings" value="true"/>
javaModelGenerator>
<sqlMapGenerator targetPackage="com._520it.mybatis.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
sqlMapGenerator>
<javaClientGenerator targetPackage="com._520it.mybatis.mapper" type="ANNOTATEDMAPPER" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
javaClientGenerator>
<table tableName="userinfo" >
<property name="constructorBased" value="false"/>
<property name="ignoreQualifiersAtRuntime" value="false"/>
<property name="immutable" value="false"/>
<property name="modelOnly" value="false"/>
<property name="selectAllOrderByClause" value="age desc,username asc"/>
<property name="useActualColumnNames" value="false"/>
<columnOverride column="username">
<property name="property" value="userName"/>
columnOverride>
table>
context>
generatorConfiguration>
将生成的代码直接拷贝到工程当中
2.4 对于xxxExample对象的理解
xxxExample是用于为接口 + 映射文件所形成的操作对象提供查询、增加、修改条件的,这一系列的操作都只针对单表操作,能过类对象进行相关的设置,极大简化的查询条件。
具体代码示例:希望能举一反三
@Test
public void test375() {
StudentExample studentExample = new StudentExample();
studentExample.or().andNameEqualTo("showme")
.andAgeEqualTo(34);
List<dream.dao.entity.Student> students = studentMapper.selectByExample(studentExample);
for (dream.dao.entity.Student student : students) {
System.out.println(student);
}
}
@Test
public void test388() {
StudentExample studentExample = new StudentExample();
studentExample.or().andIdBetween(1,2);
List<dream.dao.entity.Student> students = studentMapper.selectByExample(studentExample);
for (dream.dao.entity.Student student : students) {
System.out.println(student);
}
}
在项目开发的过程中,需要对项目过行分页显示,在mybatis中,有一个分页插件PageHelper,他是一款Mybatisr的分页插件。
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelperartifactId>
<version>5.2.1version>
dependency>
<dependency>
<groupId>com.github.jsqlparsergroupId>
<artifactId>jsqlparserartifactId>
<version>4.0version>
dependency>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="reasonable" value="true"/>
plugin>
plugins>
@RequestMapping("/emps")
public String list(@RequestParam(required = false,defaultValue = "1",value = "pn")Integer pn,Map<String,Object> map){
//引入分页查询,使用PageHelper分页功能
//在查询之前传入当前页,然后多少记录
PageHelper.startPage(pn,5);
//startPage后紧跟的这个查询就是分页查询
List<Employee> emps = employeeService.getAll();
//使用PageInfo包装查询结果,只需要将pageInfo交给页面就可以
PageInfo pageInfo = new PageInfo<>(emps,5);
//pageINfo封装了分页的详细信息,也可以指定连续显示的页数
map.put("pageInfo",pageInfo);
return "list";
}
public class PageInfo<T> implements Serializable {
private static final long serialVersionUID = 1L;
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总记录数
private long total;
//总页数
private int pages;
//结果集
private List<T> list;
//前一页
private int prePage;
//下一页
private int nextPage;
//是否为第一页
private boolean isFirstPage = false;
//是否为最后一页
private boolean isLastPage = false;
//是否有前一页
private boolean hasPreviousPage = false;
//是否有下一页
private boolean hasNextPage = false;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;
//导航条上的第一页
private int navigateFirstPage;
//导航条上的最后一页
private int navigateLastPage;
public PageInfo() {
}
/**
* 包装Page对象
*
* @param list
*/
public PageInfo(List<T> list) {
this(list, 8);
}
/**
* 包装Page对象
*
* @param list page结果
* @param navigatePages 页码数量
*/
public PageInfo(List<T> list, int navigatePages) {
if (list instanceof Page) {
Page page = (Page) list;
this.pageNum = page.getPageNum();
this.pageSize = page.getPageSize();
this.pages = page.getPages();
this.list = page;
this.size = page.size();
this.total = page.getTotal();
//由于结果是>startRow的,所以实际的需要+1
if (this.size == 0) {
this.startRow = 0;
this.endRow = 0;
} else {
this.startRow = page.getStartRow() + 1;
//计算实际的endRow(最后一页的时候特殊)
this.endRow = this.startRow - 1 + this.size;
}
} else if (list instanceof Collection) {
this.pageNum = 1;
this.pageSize = list.size();
this.pages = this.pageSize > 0 ? 1 : 0;
this.list = list;
this.size = list.size();
this.total = list.size();
this.startRow = 0;
this.endRow = list.size() > 0 ? list.size() - 1 : 0;
}
if (list instanceof Collection) {
this.navigatePages = navigatePages;
//计算导航页
calcNavigatepageNums();
//计算前后页,第一页,最后一页
calcPage();
//判断页面边界
judgePageBoudary();
}
}
.......
}