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的主配置文件时,如果一段代码需要重复利用的话,可以用模板来创建!
在这里对一个数据库进行设计;
<?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>
</configuration>
<?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>
<!-- TODO:配置当前项目中可能用到的所有环境!
default:默认使用的环境是哪个?值指向里面的某一个-->
<environments default="hello">
<!-- TODO:配置某一个具体数据库的环境,环境可以有多个!
id:该环境的标识符!-->
<environment id="hello">
<!-- TODO:事务管理器,他的作用是配置事务管理器
type:事务管理器的类型,取值有两种:jdbc( 简单的jdbc事务操作),JDBC默认是关闭提交事务的!conn.setAutoCommit(false)
managed:将事务交给其他的框架或容器操作,如Spring-->
<transactionManager type="jdbc"></transactionManager>
<!-- TODO:配置数据库的连接信息
type:配置数据源。他有三个类型
UNPOOLED:使用的简单的JDBC配置,没有使用连接池技术。只有简单的连接配置
POOLED:使用了连接池技术
JNDI:通过外部容器来获取连接-->
<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文件)
<?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">
<!--如果采取的配置文件 + 接口的方式,那么namespace必须是接口的类全名-->
<mapper namespace="abc">
<!-- TODO:
insert:表示执行添加方法!
id:表示执行添加操作的方法名,该方法名类似类中的方法名,是一个命名标识
如果是纯配置文件,没接口,方法名可以随便写,但必须唯一;
如果配置文件+接口的形式,id方法名的值必须与接口中一致!
这里所说的接口:是指
parameterType:表示方法的参数类型
如果是参数对象,可以使用类的全名
如果参数是变通的数据,可以使用mybatis中的别名
标签体:使用缩写的SQL语句!
使用#{XXX}表示点位符
如果参数是对象,那位占位符XXX用自定义对象中的属性名
如果参数是普通数据类型,那么占位符XXX名也就是参数名,用的是参数名这个标识指向的数据-->
<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>
<!-- TODO:在主配置文件中,注册一个映射文件,可以有多个
resource:映射文件的路径,写的是src中的路径-->
<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>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</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 = yourmail@gmail.com
log4j.appender.MAIL.SMTPHost = smtp.gmail.com
log4j.appender.MAIL.Subject = Log4J Message
log4j.appender.MAIL.To = yourmail@gmail.com
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);
}
<?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="com.dream.seeker.dao.InsertUser">
<!-- TODO:
insert:表示执行添加方法!
id:表示执行添加操作的方法名,该方法名类似人类中的方法名,是一个命名标识
如果是纯配置文件,没接口,方法名可以随便写,但必须唯一;
如果配置文件+接口,id方法名的值必须与接口中一致!
parameterType:表示方法的参数类型
如果是参数对象,可以使用类的全名
如果参数是变通的数据,可以使用mybatis中的别名
标签体:使用缩写的SQL语句!
使用#{XXX}表示点位符
如果参数是对象,那位占位符XXX用自定义对象中的属性名
如果参数是普通数据类型,那么占位符XXX名也就是参数名,用的是参数名这个标识指向的数据-->
<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,具体示例如下:
<!-- TODO:设置别名 -->
<typeAliases>
<!-- TODO:为某一个类去配置别名 -->
<!-- <typeAlias type="com.dream.seeker.entity.impl.GradleUser" alias="GradleUser"/>-->
<!-- TODO:package:为包下面的所有类取别名
name:指定包名,该包下所有类的别名,就是其自身的类名,原则上不区分大小上,但建设采用首字母大写-->
<package name="com.dream.seeker.entity.impl"/>
</typeAliases>
设置好别名后,那么就可以在MyBatis映射文件中使用别名了,下面的代码是基于接口 + 配置文件的方式定义的mapper(映射)文件,其中namesapce是接口名,id指的是接口的抽象方法名,而parameterType这个地方通过在主配置文件中定义的别名,可以简化许多代码。
<?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="com.dream.seeker.dao.InsertUser">
<!-- TODO:
insert:表示执行添加方法!
id:表示执行添加操作的方法名,该方法名类似人类中的方法名,是一个命名标识
如果是纯配置文件,没接口,方法名可以随便写,但必须唯一;
如果配置文件+接口,id方法名的值必须与接口中一致!
parameterType:表示方法的参数类型
如果是参数对象,可以使用类的全名
如果参数是变通的数据,可以使用mybatis中的别名
标签体:使用缩写的SQL语句!
使用#{XXX}表示点位符
如果参数是对象,那位占位符XXX用自定义对象中的属性名
如果参数是普通数据类型,那么占位符XXX名也就是参数名,用的是参数名这个标识指向的数据-->
<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">
<!-- 此处所有SQL语句中的点位符的值都来自于对象参数中的属性 -->
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">
<!--此处的id,也就是接口方法中形参的id -->
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>
<!-- TODO:定义sql代码段,免去后面重复写字段的名字 -->
<sql id="studentFiled">
id,name,age
</sql>
<!-- TODO:用include标识符来调用sql片标,refid为指定哪个字段集 -->
<select id="selectAll" resultType="Student">
select <include refid="studentFiled"></include> from student
</select>
整体映射文件代码如下:包含了更新,删除、查询的所有语句
<?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="dream.dao.batis.BatisOne">
<!-- TODO:定义sql代码段,免去后面重复写字段的名字 -->
<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>
<!-- TODO:用include标识符来调用sql片标,refid为指定哪个字段集 -->
<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>
<!-- TODO:外部定义的映射标签
1:id:是一个标识符,在后面sql语句中被调用
2:type:要映射到的返回对象
3.区分主键和普通字段,主键单独用id标签,普通字段用result标签
4.proerties是自定义对象中的属性,column是数据库表中的字段-->
<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);
<!-- TODO:使用索引,也可以直接使用接口方法中的参数名,多参,无需在语句中定义参数类型parameterType -->
<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);
<!-- TODO: 使用@Param注解,点位符中使用的是注解中定义的名字-->
<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);
<!-- TODO:自定义对象传递,需要定parameterType,参数和返回值可以是同一个对象,如果对象中的属性够用的话 -->
<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);
<!-- TODO:使用Map集合,点位符使用的是Map中的Key对应的值 -->
<select id="selectMapStudent" resultType="ClassStudent">
select name,age,address from dbuser where age=#{age}
</select>
**总结分析:**在一般情况下,使用注解,和自下定义对象的方法比较居多,具体情况还得根据情况而定。
模糊查询主要有二种:一种是手动模糊查询;另一种是使用Bind进行查询
使用手动模糊查询,模糊查询的匹配符是在外面进行定义的,映射文件里厕所SQL语句接收的参数仍是接口中定义的方法形参数标识名
List<Student> selectOut(String s);
<!-- 在映射文件中的SQL语句中,只要不是不是自定义对象做为参数,都可以在语句中不写ParameterType -->
<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="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="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>
嵌套使用手动映射代码,也有两种情况,一是直接使用同一文件中的映射代码,另一个是使用外部文件的映射代码
<!-- todo:这个地方是将三个代码都放到一块了 -->
<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.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--配置参考地址:http://www.mybatis.org/generator/configreference/xmlconfig.html-->
<!--配置参考地址http:///pk490525/article/details/16819307-->
<!--配置参考:http:///lirui874125/article/details/49589563-->
<!--配置参考:http://www.jianshu.com/p/e09d2370b796-->
<!-- 引入配置文件 -->
<!--<properties resource="init.properties"/>-->
<!-- 指定数据连接驱动jar地址 -->
<!-- <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />-->
<!--
context:生成一组对象的环境
id:必选,上下文id,用于在生成错误时提示
defaultModelType:指定生成对象的样式
1,conditional:类似hierarchical;
2,flat:所有内容(主键,blob)等全部生成在一个对象中;
3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class)
targetRuntime:
1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
introspectedColumnImpl:类全限定名,用于扩展MBG
-->
<context id="Mysql" targetRuntime="MyBatis3Simple">
<!--https://mapperhelper.github.io/docs/3.usembg/,自动生成代码的通用mapper插件-->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
<!-- caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true -->
<!-- <property name="caseSensitive" value="true"/>-->
</plugin>
<!-- 注释 -->
<commentGenerator >
<!-- 是否取消自动生成的注释 -->
<!--<property name="suppressAllComments" value="false"/>-->
<!-- 是否生成注释代时间戳-->
<!-- <property name="suppressDate" value="true" />-->
</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">
<!-- 针对mysql数据库 -->
<property name="useInformationSchema" value="true"/>
</jdbcConnection>
<!-- 类型转换 -->
<javaTypeResolver >
<!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- 生成实体类地址 -->
<javaModelGenerator targetPackage="${targetModelPackage}" targetProject="${targetJavaProject}">
<!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
<!--<property name="enableSubPackages" value="true" />-->
<!-- 是否针对string类型的字段在set的时候进行trim调用 -->
<!--<property name="trimStrings" value="true" />-->
</javaModelGenerator>
<!-- 生成mapxml文件 -->
<sqlMapGenerator targetPackage="${targetXMLPackage}" targetProject="${targetResourcesProject}">
<!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
<!-- <property name="enableSubPackages" value="true" />-->
</sqlMapGenerator>
<!-- 生成mapxml对应client,也就是接口dao -->
<javaClientGenerator type="XMLMAPPER" targetPackage="${targetMapperPackage}" targetProject="${targetJavaProject}">
<!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
<!--<property name="enableSubPackages" value="true" />-->
</javaClientGenerator>
<!-- 配置表信息 -->
<!-- schema即为数据库名 tableName为对应的数据库表 domainObjectName是要生成的实体类 enable*ByExample
是否生成 example类 -->
<!--<table schema="jack" tableName="ALLTYPES" domainObjectName="Customer" >-->
<!-- <property name="useActualColumnNames" value="true"/>
<generatedKey column="ID" sqlStatement="DB2" identity="true" />
<columnOverride column="DATE_FIELD" property="startDate" />-->
<!-- 忽略列,不生成bean 字段 -->
<!--<ignoreColumn column="FRED" />-->
<!-- 指定列的java数据类型 -->
<!-- <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />-->
<!-- </table>-->
<table tableName="student" domainObjectName="Student" />
</context>
</generatorConfiguration>
<?xml version="1.0" encoding="UTF-8"?>
<!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">
<!-- 生成注释为false 不生成为true 【不生成注释时会被重复写入导致报错】 -->
<commentGenerator>
<property name="suppressAllComments" value="false"/>
</commentGenerator>
<!--jdbc的数据库连接 -->
<jdbcConnection
driverClass="${driverClassName}"
connectionURL="${url}"
userId="${username}"
password="${password}">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
targetPackage 指定生成的model生成所在的包名
targetProject 指定在该项目下所在的路径
-->
<!--<javaModelGenerator targetPackage="com.mmall.pojo" targetProject=".\src\main\java">-->
<javaModelGenerator targetPackage="com.dream.seeker" targetProject="./src/main/java">
<!-- 是否允许子包,即targetPackage.schemaName.tableName -->
<property name="enableSubPackages" value="false"/>
<!-- 是否对model添加 构造函数 -->
<property name="constructorBased" value="true"/>
<!-- 是否对类CHAR类型的列的数据进行trim操作 -->
<property name="trimStrings" value="true"/>
<!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 -->
<property name="immutable" value="false"/>
</javaModelGenerator>
<!--mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
<sqlMapGenerator targetPackage="mappers" targetProject="./src/main/resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- targetPackage:mapper接口dao生成的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.*.dao" targetProject="./src/main/java">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<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>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
<!-- 可以用于加载配置项或者配置文件,在整个配置文件中就可以使用${propertyKey}的方式来引用配置项
resource:配置资源加载地址,使用resource,MBG从classpath开始找,比如com/myproject/generatorConfig.properties
url:配置资源加载地质,使用URL的方式,比如file:///C:/myfolder/generatorConfig.properties.
注意,两个属性只能选址一个;
另外,如果使用了mybatis-generator-maven-plugin,那么在pom.xml中定义的properties都可以直接在generatorConfig.xml中使用
<properties resource="" url="" />
-->
<properties resource="dbcp-config.properties"/>
<!-- 在MBG工作的时候,需要额外加载的依赖包
location属性指明加载jar/zip包的全路径
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
-->
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
<!--
context:生成一组对象的环境
id:必选,上下文id,用于在生成错误时提示
defaultModelType:指定生成对象的样式
1,conditional:类似hierarchical;
2,flat:所有内容(主键,blob)等全部生成在一个对象中;
3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class)
targetRuntime:
1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
introspectedColumnImpl:类全限定名,用于扩展MBG
-->
<context id="mysql" defaultModelType="hierarchical" targetRuntime="MyBatis3Simple" >
<!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;
一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖
-->
<property name="autoDelimitKeywords" value="false"/>
<!-- 生成的Java文件的编码 -->
<property name="javaFileEncoding" value="UTF-8"/>
<!-- 格式化java代码 -->
<property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
<!-- 格式化XML代码 -->
<property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
<!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<!-- 必须要有的,使用这个配置链接数据库
@TODO:是否可以扩展
-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql:///pss" userId="root" password="admin">
<!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 -->
</jdbcConnection>
<!-- java类型处理器
用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型;
-->
<javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
<!--
true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
false:默认,
scale>0;length>18:使用BigDecimal;
scale=0;length[10,18]:使用Long;
scale=0;length[5,9]:使用Integer;
scale=0;length<5:使用Short;
-->
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- java模型创建器,是必须要的元素
负责:1,key类(见context的defaultModelType);2,java类;3,查询类
targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录
-->
<javaModelGenerator targetPackage="com._520it.mybatis.domain" targetProject="src/main/java">
<!-- for MyBatis3/MyBatis3Simple
自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter;
-->
<property name="constructorBased" value="false"/>
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>
<!-- for MyBatis3 / MyBatis3Simple
是否创建一个不可变的类,如果为true,
那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类
-->
<property name="immutable" value="false"/>
<!-- 设置一个根对象,
如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项
注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括:
1,属性名相同,类型相同,有相同的getter/setter方法;
-->
<property name="rootClass" value="com._520it.mybatis.domain.BaseDomain"/>
<!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成SQL map的XML文件生成器,
注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),
或者只使用Mapper接口+Annotation,所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
targetPackage/targetProject:同javaModelGenerator
-->
<sqlMapGenerator targetPackage="com._520it.mybatis.mapper" targetProject="src/main/resources">
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口
targetPackage/targetProject:同javaModelGenerator
type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER
-->
<javaClientGenerator targetPackage="com._520it.mybatis.mapper" type="ANNOTATEDMAPPER" targetProject="src/main/java">
<!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
<property name="enableSubPackages" value="true"/>
<!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查
<property name="rootInterface" value=""/>
-->
</javaClientGenerator>
<!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
选择的table会生成一下文件:
1,SQL map文件
2,生成一个主键类;
3,除了BLOB和主键的其他字段的类;
4,包含BLOB的类;
5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
6,Mapper接口(可选)
tableName(必要):要生成对象的表名;
注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
4,否则,使用指定的大小写格式查询;
另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;
可选:
1,schema:数据库的schema;
2,catalog:数据库的catalog;
3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName
4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
5,enableInsert(默认true):指定是否生成insert语句;
6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
13,modelType:参考context元素的defaultModelType,相当于覆盖;
14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性
注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写;
-->
<table tableName="userinfo" >
<!-- 参考 javaModelGenerator 的 constructorBased属性-->
<property name="constructorBased" value="false"/>
<!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; -->
<property name="ignoreQualifiersAtRuntime" value="false"/>
<!-- 参考 javaModelGenerator 的 immutable 属性 -->
<property name="immutable" value="false"/>
<!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 -->
<property name="modelOnly" value="false"/>
<!-- 参考 javaModelGenerator 的 rootClass 属性
<property name="rootClass" value=""/>
-->
<!-- 参考javaClientGenerator 的 rootInterface 属性
<property name="rootInterface" value=""/>
-->
<!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog
<property name="runtimeCatalog" value=""/>
-->
<!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema
<property name="runtimeSchema" value=""/>
-->
<!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename
<property name="runtimeTableName" value=""/>
-->
<!-- 注意,该属性只针对MyBatis3Simple有用;
如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件;
-->
<property name="selectAllOrderByClause" value="age desc,username asc"/>
<!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN_DATE,生成的属性名字就是BORN_DATE,而不会是bornDate -->
<property name="useActualColumnNames" value="false"/>
<!-- generatedKey用于生成生成主键的方法,
如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,该元素可选
column:主键的列名;
sqlStatement:要生成的selectKey语句,有以下可选项:
Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
DB2 :相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
DB2_MF :相当于selectKey的SQL为:SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
Derby :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL()
HSQLDB :相当于selectKey的SQL为:CALL IDENTITY()
Informix :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1
MySql :相当于selectKey的SQL为:SELECT LAST_INSERT_ID()
SqlServer :相当于selectKey的SQL为:SELECT SCOPE_IDENTITY()
SYBASE :相当于selectKey的SQL为:SELECT @@IDENTITY
JDBC :相当于在生成的insert元素上添加useGeneratedKeys="true"和keyProperty属性
<generatedKey column="" sqlStatement=""/>
-->
<!--
该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候,
比如列名为:CUST_ID,CUST_NAME,CUST_EMAIL,CUST_ADDRESS等;
那么就可以设置searchString为"^CUST_",并使用空白替换,那么生成的Customer对象中的属性名称就不是
custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email;
注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的,
如果使用了columnOverride元素,该属性无效;
<columnRenamingRule searchString="" replaceString=""/>
-->
<!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性;
column:要重新设置的列名;
注意,一个table元素中可以有多个columnOverride元素哈~
-->
<columnOverride column="username">
<!-- 使用property属性来指定列要生成的属性名称 -->
<property name="property" value="userName"/>
<!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名
<property name="javaType" value=""/>
-->
<!-- jdbcType用于指定该列的JDBC类型
<property name="jdbcType" value=""/>
-->
<!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名
注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler
只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com._520it.mybatis.MyTypeHandler}的参数描述
<property name="jdbcType" value=""/>
-->
<!-- 参考table元素的delimitAllColumns配置,默认为false
<property name="delimitedColumnName" value=""/>
-->
</columnOverride>
<!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现
column:指定要忽略的列的名字;
delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false
注意,一个table元素中可以有多个ignoreColumn元素
<ignoreColumn column="deptId" delimitedColumnName=""/>
-->
</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.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.1</version>
</dependency>
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>4.0</version>
</dependency>
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- config params as the following -->
<!--<!–分页参数合理化 –>-->
<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();
}
}
.......
}