Java程序是通过JDBC(Java Data Base Connectivity)连接数据库的,JDBC是由SUN公司提出的 一系列规范,但是它只定义了接口的规范,而具体的实现是交给了各个数据库厂商去实现的, JDBC是一种典型的桥接模式
JDBC编程步骤
使用JDBC编程需要连接数据库,注册驱动和数据库信息
操作Connection,打开Statement对象
通过Statement执行SQL,返回结果到ResultSet对象
使用ResultSet读取数据,然后通过代码转换为具体的POJO对象
关闭数据库相关资源
JDBC弊端
工作量大
编程复杂
SQL代码和JAVA代码混在一起
ORM取代了JDBC,所有的ORM模型都是基于JDBC进行封装的,不同ORM模型对JDBC封装的强 度是不一样的
ORM:对象关系映射(Object Relational Mapping),简单的说ORM框架就是数据库的表和简单 Java对象的映射关系模型,我们通过这层映射关系就可以简单迅速地把数据库表的数据转化为 POJO
java程序---->映射配置---->数据库
java程序-<----映射配置<----数据库
目前较为流行的ORM框架:
Hibernate:ORM鼻祖,功能强大,能自动生成SQL,但是使用起来较为复杂
JPA:与Hibernate为同一作者
MyBatis:轻量级,简单易用
Hibername一问世就成为了Java世界首选的ORM框架,它是建立在POJO和数据库 表模型的直接映射关系上的
Hibernate应用程序<------hbm.xml<-------DataBase
Hibernate应用程序------>hbm.xml------->DataBase
Hibernate优势:
消除了代码的映射规则,它全部被分离到了XML或者注解里面去配置
无需再管理数据库连接,它也可以配置在XML里面
一个会话中,不要操作多个对象,只要操作Session对象即可
关闭资源只需要关闭一个Session便可
Hibernate缺陷:
全表映射带来了不便,比如更新时需要发送所有的字段
无法根据不同的条件组装不同的SQL
对多表关联和复杂SQL查询支持较差,需要自己写SQL,返回后,需要自己将数据组装为 POJO
不能有效支持存储过程
虽然有HQL,但是性能较差, 大型互联网系统往往需要优化SQL,而hibernate做不到
为了解决Hibernate的不足,一个半自动映射的框架MyBatis应运而生 mybatis是一个java持久层框架,它封装少、高性能·可优化、维护简单等优点成为了目前java移动 互联网网站服务的首选持久层框架,它特别适合分布式和大数据网络数据库编程 之所以称它为半自动,是因为它需要手工匹配提供POJO、SQL和映射关系,而全表映射的 Hibernate只需要提供POJO和映射关系便可
历史
Mybaits的前身是Apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了 google code 并且改名为Mybatis,2013年11月迁移到Github,目前 mybaits是由Github维护的
名称
iBatis一词来源于“internet”和“abatis”的组合 mybaits所需要提供的映射文件包含以下三个部分
SQL
映射规则
POJO
什么时候用mybaits
Hibernate只适用于场景不太复杂,要求性能不太苛刻的时候
Mybatis拥有动态列,动态表名,存储过程支持,同时提供了简易的缓存、日 志、级联,但是它的缺陷是需要你提供映射规则和sql,所以它的开发工作量 比Hibernate略大一些
JDK要求:>=1.8
1、创建实体类(和表对应)
package com.turing.mybatis.entity; /** * 学生实体类--和表对应 * @author fred * */ public class Student { private int id; private String name; private int age; private String email; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", age=" + age + ", email=" + email + "]"; } }
2、引入jar包(mybatis的jar包和mysql的驱动jar包)
注意:如mybatis的版本比较高时,数据库的驱动也要使用高版本,例如mybatis版本为3.5.6 时,mysql驱动包需使用5.1.48版本,否则就会报错!
3、创建资源目录包(放在资源目录中的文件和放在根目录是一样的)
4、创建mybatis全局配置文件:mybatis-config.xml(名称可以随便取)
POOLED:(POOLED实则就是数据源的一个实例名)
5、创建测试类,获取SqlSessionFactory对象
package com.turing.mybatis.entity; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class Test { public static void main(String[] args) { try { //通过XML配置文件来构建SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //获取SqlSessionq(其实就相当于我们以前的Connection对象) //打开session SqlSession session = sqlSessionFactory.openSession(); try { Student stu = session.selectOne("com.turing.haha.selectStu", 1); System.out.println(stu); } finally { //关闭session session.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
6、创建映射文件StudentMapper.xml
使用日志可以监控SQL的执行情况,目前日志框架也非常成熟,如log4j。
1、导入jar包(可以去mybatis的lib包下拷贝)
2、加入配置文件,名称必须为:log4j.properties。(不需要手写,可以去网上拷贝一个 模板)
log4j.rootLogger=DEBUG,Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p - %m%n log4j.logger.org.apache=INFO
3、效果(在控制台或指定文件中,输出执行过程)
1、配置文件头(固定写法,建议不要手写,从官网拷贝,或自己保存为一个模板,其中dtd为标签约束文 件,当电脑联网会自动下载,否则没有提示)
2、configuration
...
配置根节点,不能缺少,所有标记都必须在该标签下
3、environments
环境列表,表示在该标签下,可以配置多个environment标签,default可以指定一个默认环境。 使用场景:例如配置两个environment,一个为开发环境,一个为上线环境,开发时使用mysql,上线时使 用oracle。这样不用改变明细代码,而只用改变default就可以随意切换,非常方便
UNPOOLED:不使用连接池技术
POOLED:使用连接池技术
JNDI:使用JNDI的方式
4、mappers:映射器
1)概念
映射器是MyBatis最复杂、最核心的组件。
映射器提供DAO层接口到mapper.xml文件的映射,我们只需要调用DAO层的方法,就可以以执行对应 的SQL语句,这里用到的是java的动态代理特性。
mappers:配置mapper列表,专门放置我们写好的xxxMapper.xml配置文件。
mapper:resource指定xxxMapper.xml配置文件的所在路径。
2)配置映射器
3)使用注解配置,就不需要xxxMapper.xml配置文件,但需要配置接口或扫描接口
public interface StudentMapper { //根据id查询学生对象 @Select("select * from student where id=#{id}") Student findById(int id); }
注意:
注解虽然使用方便,但并不推荐,毕竟SQL写在代码中,产生了高耦合,维护起来不方便。
实际开发中,也可以混合使用,重要的复杂的写在xml中,不重要的简单的可以使用注解。
4)批量注册
批量注册:指定包名,全包扫描,但xml映射文件要和映射接口放在同一包下
我们通常可以在资源包下,创建一个和映射接口一模一样的文件。
5、properties:配置全局参数
properties可以配置全局参数,例如可以把jdbc的四要素配置在一个properties后缀的文件中
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///t146 jdbc.username=root jdbc.password=root
6、typeAliases:别名设置
注意:
不管是通过 typeAlias 标签配置,还是通过package 标签配置的别名,在mapper.xml文件中使 用的时候,是可以忽略大小写的。
如果不手动设置别名,默认是类名的小写。
如果配置了注解别名,注解别名会覆盖上面的所有配置。
@Alias("stu") public class Student { ... }
7、settings:设置选项(本章不作为重点)
...
其它设置选项和对应的值:
配置项 | 作用 | 配置选项 | 默认值 |
---|---|---|---|
cacheEnabled | 该配置影响所有映射 器中配置缓存的全局 开关 | TRUE|FALSE | TRUE |
lazyLoadingEnabled | 延迟加载的全局开 关。当开启时,所有 关联对象都会延迟加 载。在特定关联关系 中可通过设置 fetchType 属性来覆 盖该项的开关状态 | TRUE|FALSE | FALSE |
aggressiveLazyLoading | 当启用时,对任意延 迟属性的调用会使带 有延迟加载属性的对 象完整加载;反之, 每种属性将会按需加 载 | TRUE|FALSE | 版本3.4.1 (不包含) 之前 true,之后 false |
multipleResultSetsEnabled | 是否允许单一语句返 回多结果集(需要兼 容驱动) | TRUE|FALSE | TRUE |
useColumnLabel | 使用列标签代替列 名。不同的驱动会有 不同的表现,具体可 参考相关驱动文档或 通过测试这两种不同 的模式来观察所用驱 动的结果 | TRUE|FALSE | TRUE |
useGeneratedKeys | 允许JDBC 支持自动生 成主键,需要驱动兼 容。如果设置为 true,则这个设置强 制使用自动生成主 键,尽管一些驱动不 能兼容但仍可正常工 作(比如 Derby) | TRUE|FALSE | FALSE |
autoMappingBehavior | 指定 MyBatis 应如何 自动映射列到字段或 属性。 NONE 表示取消自动 映射。 PARTIAL 表示只会自 动映射,没有定义嵌 套结果集和映射结果 集。 FULL 会自动映射任意 复杂的结果集(无论 是否嵌套) | NONE、PARTIAL、FULL | PARTIAL |
autoMappingUnkno wnColumnBehavior | 指定自动映射当中未 知列(或未知属性类 型)时的行为。 默认 是不处理,只有当日 志级别达到 WARN 级别或者以下,才会 显示相关日志,如果 处理失败会抛出 SqlSessionException 异常 | NONE、WARNING、FAILING | NONE |
defaultExecutorType | 配置默认的执行器。 SIMPLE 是普通的执 行器;REUSE 会重用 预处理语句 (prepared statements); BATCH 执行器将重用 语句并执行批量更新 | SIMPLE、REUSE、BATCH | SIMPLE |
defaultStatementTimeout | 设置超时时间,它决 定驱动等待数据库响 应的秒数 | 任何正整数 | Not Set (null) |
defaultFetchSize | 设置数据库驱动程序 默认返回的条数限 制,此参数可以重新 设置 | 任何正整数 | Not Set (null) |
safeRowBoundsEnabled | 允许在嵌套语句中使 用分页 (RowBounds)。如 果允许,设置 false | TRUE|FALSE | FALSE |
safeResultHandlerEnabled | 允许在嵌套语句中使 用分页 (ResultHandler)。 如果允许,设置false | TRUE|FALSE | TRUE |
mapUnderscoreToCamelCase | 是否开启自动驼峰命 名规则映射,即从经 典数据库列名 A_COLUMN 到经 典 Java 属性名 aColumn 的类似映射 | TRUE|FALSE | FALSE |
localCacheScope | MyBatis 利用本地缓 存机制(Local Cache)防止循环引 用(circular references)和加速 联复嵌套査询。 默认值为 SESSION, 这种情况下会缓存一 个会话中执行的所有 查询。若设置值为 STATEMENT,本地 会话仅用在语句执行 上,对相同 SqlScssion 的不同调 用将不会共享数据 | SESSION|STATEMENT | SESSION |
jdbcTypeForNull | 当没有为参数提供特 定的 JDBC 类型时, 为空值指定 JDBC 类 型。某些驱动需要指 定列的 JDBC 类型, 多数情况直接用一般 类型即可,比如 NULL、VARCHAR 或 OTHER | NULL、VARCHAR、OTHER | OTHER |
lazyLoadTriggerMethods | 指定哪个对象的方法 触发一次延迟加载 | ------------------------------ | equals、clone、hashCode、 toString |
defaultScriptingLanguage | 指定动态 SQL 生成的 默认语言 | ------------------------------- | org.apache.ibatis .script.ing.xmltags .XMLDynamicLanguageDriver |
callSettersOnNulls | 指定当结果集中值为 null 时,是否调用映 射对象的 setter(map 对象时 为 put)方法,这对 于 Map.kcySet() 依 赖或 null 值初始化时 是有用的。注意,基 本类型(int、 boolean 等)不能设 置成 null | TRUE|FALSE | FALSE |
logPrefix | 指定 MyBatis 增加到 日志名称的前缀 | 任何字符串 | Not set |
loglmpl | 指定 MyBatis 所用日 志的具体实现,未指 定时将自动査找 | SLF4J|LOG4J LOG4J2|JDK_LOGGING |COMMONS_LOGGING |ST DOUT_LOGGING| NO_LOGGING | Not set |
proxyFactory | 指定 MyBatis 创建具 有延迟加栽能力的对 象所用到的代理工具 | CGLIB|JAVASSIST | JAVASSIST (MyBatis 版本为 3.3 及以上的) |
vfsImpl | 指定 VFS 的实现类 | 提供 VFS 类的全限定名,如果存在多 个,可以使用逗号分隔 | Not set |
useActualParamName | 允许用方法参数中声 明的实际名称引用参 数。要使用此功能, 项目必须被编译为 Java 8 参数的选择。 (从版本 3.4.1 开始 可以使用) | TRUE|FALSE | TRUE |
settings 的配置项很多,但是真正用到的不会太多,我们把常用的配置项研究清楚就可以了,比如关于 缓存的 cacheEnabled,关于级联的 lazyLoadingEnabled 和 aggressiveLazy Loading,关于自动映 射的 autoMappingBehavior 和 mapUnderscoreToCamelCase,关于执行器类型的 defaultExecutorType 等。
这里给出一个全量的配置样例,如下所示。
使用Mapper接口的方式完成增删改查 接口定义方法和映射配置如下:
1、Mapper接口
public interface StudentMapper { //根据id查询学生 //@Select(value="select * from student where s_id=#{id}") //使用注解的方式 Student findById(int id); //根据id查询,返回Map类型 MapfindById2(int id); //根据id和name查询学生 Student findByIdAndName(@Param("id")int id,@Param("name")String name); //根据Map查询学生 Student findByMap(Map map); //查询所有学生,返回List //@Select("select * from student") List findAll(); //查询所有学生,返回Map //如果返回的类型是Map,且超时一条时,需要明确key用什么字段 @MapKey("s_id") //表示用编号作为key,value就是对象 Map findAll2(); //模糊查询 //@Select("select * from student where s_name like concat('%',#{name},'%')") List findByLike(String name); //添加学生 //value仅限一个参数才可以省略 //@Insert(value="insert into student values(#{sId},#{sName},#{sAge},#{tId})") //@Options(useGeneratedKeys=true,keyColumn="s_id",keyProperty="sId") int add(Student stu); //修改学生 //@Update("update student set s_name=#{sName},s_age=#{sAge},t_id=#{tId} where s_id=#{sId}") boolean update(Student stu); //删除学生 //@Delete("delete from student where s_id=#{id}") int delete(int id); }
2、Mapper.xml
insert into student values(#{sId},#{sName},#{sAge},#{tId}) update student set s_name=#{sName},s_age=#{sAge},t_id=#{tId} where s_id=# {sId} delete from student where s_id=#{id}
3、细节问题
1)parameterType(参数类型)可以省略,resultType(返回类型)不能省略。 2)mybatis允许增删改直接定义以下类型返回值
只需要在接口上设置返回值类型,mybatis就可以自动为我们返回相应类型的执行结果。
Integer、Long、Boolean、void包装类和基本类型都支持。 3)事务提交
SqlSession默认是手动提交事务,如需自动提交,在参数中设置true。
sqlSessionFactory().openSession()--->手动提交事务
sqlSessionFactory().openSession(true)--->自动提交事务 4)获取添加操作后的主键值 在映射配置中增加:
useGeneratedKeys="true":使用自增主键获取主键策略
keyColumn="id":只对应的主键字段
keyProperty="id":指定对应的主键属性,也就是mybatis获取到主键值后,将这个值放 到JavaBean的哪个属性中。
例如:
insert into student values(null,#{name},#{age},#{email})
1、单个参数
mybatis不会做任何处理,使用#{参数名}的方式即可,且参数名没有要求。
也就是#{}中的参数既可以写id,也可以写其它名称。
2、多个参数
1)有多个参数的时候,用上述方法就会抛出异常:
org.apache.ibatis.exceptions.PersistenceException: Caused by: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]
2)当有多个参数的时候,mybatis会做特殊处理,把参数封装到一个Map中,key为param+下 标的方式:param1,param2,...,#{param1},#{param2}
3)使用注解
当参数很多,上述处理容易写错,也不方便,我们希望和接口中定义的方法参数名保持一致,可 以在接口参数前使用注解:@param
在接口中:
public Student getStuById2(@Param("id")Integer id,@Param("name")String name);
在映射配置中和注解的名称对应起来:
3、对象参数
如果多个参数正好和我们的实体类对应,我们就可以直接传入对象。 #{属性名}:取出传入pojo的属性值
4、Map参数
如果多个参数和我们的实体类不对应,为了方便,我们也可以传入Map类型。 #{key}:取出map中对应的值
接口:
//接口 Student findByMap(Mapmap);
映射配置:
测试类:
@Test public void testfindByMap(){ SqlSession session = sqlSessionFactory.openSession(); try{ //获取Mapper对象 StudentMapper mapper = session.getMapper(StudentMapper.class); //调用删除方法 Mapmap = new HashMap (); map.put("id", "1"); map.put("name", "曹操"); Student stu = mapper.findByMap(map); //打印 System.out.println(stu); }finally{ session.close(); } }
5、Collection作为参数【结合动态SQL去使用,循环】
如果参数类型是集合,例如List、Set、数组(Array),也会进行特殊处理,mybatis会对List 进行Map封装,需要使用key来获取,此处可以使用三种key:
#{arg0[index]}
#{collection[index]}
#{list[index]}
如果是数组:#{array[index]}
6、#{}和${}的区别
#{}:以预编译的形式,将参数设置到sql语句中,相当于PreparedStatment,防止SQL 注入
${}:取出的值直接拼接在sql语句中,相当于Statment,会有安全问题
通常情况下,我们会使用#{}居多,不过在SQL中需要拼接时,就会用到${}的方式。
例如:我们以下列代码为例,id使用${}的方式获取,name使用#{}的方式获取
运行测试类,我们看控制台的输出结果:
7、模糊查询
接口:
//根据名称模糊查询 ListfindByLike(String name);
映射配置:
测试类:
@Test public void testFindByLike(){ //调用接口中的方法 Listlist = mapper.findByLike("三"); //测试输出 System.out.println(list); }
1、查询返回List
注意:在映射配置中,返回类型依然是集合中对象的类型,而非List
2、查询返回Map
注意:在映射配置中,返回类型如果是Map,类型写map即可。
3、多条记录封装成一个Map
例如:Map
接口:
//模糊查询,返回多个Map @MapKey("id")//指定列作为返回值map的key MapfindByLike2(String str);
映射文件:
测试类:
//测试模糊查询返回多个Map @Test public void testfindByLike2(){ //打开会话,不会自动提交事务,需要手动提交 SqlSession session = sqlSessionFactory.openSession(); try{ //获取Mapper对象 StudentMapper mapper = session.getMapper(StudentMapper.class); //调用查询方法 Mapmap = mapper.findByLike2("%刘%"); //打印 System.out.println(map); }finally{ session.close(); } }
在Mapper接口中,可以使用Annotation完成SQL映射
1、@Select
相当于
@Select("select * from student") Listlist();
2、@Insert
相当于
@Insert("insert into student values(#{id},#{name},#{age},#{email})") int addStu(Student stu);
如果要获取主键id,可以使用@Option注解
@Insert("insert into student values(#{id},#{name},#{age},#{email})") @Options(useGeneratedKeys = true,keyColumn = "id",keyProperty = "id") int addStu(Student stu);
3、@Delete
相当于
@Delete("delete from student where id = #{id}") int deleteStu(Integer id);
4、@Update
相当于
@Update("update student set p_name=#{name},age=#{age},email=#{email} where id=#{id}") int updateStu(Student stu);
5、什么时候用注解?
通常情况下,如果比较简单的SQL,可以使用注解,如果比较复杂,还是建议写到配置文件 中。 在开发的时候,也可以使用注解+配置文件的混合方式。
#{}和${}的区别?
RestulType和ResultMap的区别?
MyBatis中如何实现分页查询?
MyBatis中如何实现关联查询?比如一对一、一对多 MyBatis中如何实现延迟加载?
一、#与$的区别
#{}:使用了预编译处理,底层就是使用PreparentStatment,安全。
${}:没有使用预编译处理,底层就是Statment,有SQL注入风险。
#{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符?: 对于 : INSERT INTO user (name) VALUES (#{name}); ==> INSERT INTO user (name) VALUES (?); ${} 符号的作用是直接进行字符串替换: 对于 : INSERT INTO user (name) VALUES ('${name}'); ==> INSERT INTO user (name) VALUES ('dafeige');
二、ResultMap(自定义结果映射)
常情况下,我们可能表字段和实体类的属性不一致,或者多表查询,都需要我们来配置一个返回类型
1、在映射配置文件中配置:
2、使用:
注意:resultType和resultMap不要同时使用。
3、Mybatis中javaType和jdbcType对应关系
JDBCType JavaType CHAR String VARCHAR String LONGVARCHAR String NUMERIC java.math.BigDecimal DECIMAL java.math.BigDecimal BIT boolean BOOLEAN boolean TINYINT byte SMALLINT short INTEGER int BIGINT long REAL float FLOAT double DOUBLE ouble BINARY byte[] VARBINARY byte[] LONGVARBINARY byte[] DATE java.sql.Date TIME java.sql.Time TIMESTAMP java.sql.Timestamp CLOB Clob BLOB Blob ARRAY Array DISTINCT mapping of underlying type
STRUCT Struct
REF Ref
DATALINK java.net.URL
4、RestulType和ResultMap的区别?
RestulType使用我们已经存在的实体类型(Pojo),使用RestulType的前期是字段名和属性名要一致。
RestulType不能用在关联查询上。
ResultMap是我们自定义的一个结果映射,用法更加灵活,我们可以自己来指定字段名和属性名的对应关系。
ResultMap还可以来配置MyBatis中的关联查询。
三、分页查询
1、使用SQL的方式
Mapper接口中:
//分页查询方式一:使用SQL //SELECT * FROM student LIMIT 5,5; 第一个参数:下标(下标从0开始);第二个参数:长度,显示几条 @Select("SELECT * FROM student LIMIT #{pageNow},#{pageSize}") ListfindAllPage1(@Param("pageNow")int pageNow,@Param("pageSize")int pageSize);
测试类:
@Test public void testFindAllPage1(){ //当前页 int pageNow = 2; //每页显示几条 int pageSize = 5; //打开会话 SqlSession openSession = sqlSessionFactory.openSession(); //获取mapper StudentMapper mapper = openSession.getMapper(StudentMapper.class); //调用接口方法 Listlist = mapper.findAllPage1((pageNow-1)*pageSize, pageSize); //输出 for (Student stu : list) { System.out.println(stu); } //关闭会话 openSession.close(); }
2、使用MyBatis的方式
Mapper配置文件中:
测试类:
//测试分页查询2 @Test public void testFindByPage2(){ int pageNow = 1; int pageSize = 5; RowBounds rowBounds = new RowBounds((pageNow-1)*pageSize, pageSize); List
四、一对一关联(方式一:分步查询)
1、关联关系(association)
一对一: 人和身份证、丈夫和妻子...
2、MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:
property:对象属性的名称
javaType:对象属性的类型
column:所对应的外键字段名称
select:使用另一个查询封装的结果
3、创建表和数据
-- 添加老师表 CREATE TABLE `teacher` ( `id` int(10) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 -- 修改学生表,添加tid字段 ALTER TABLE student ADD COLUMN tid INT(10); -- 修改学生表,添加外键约束 ALTER TABLE student ADD CONSTRAINT fk_tid FOREIGN KEY(tid) REFERENCES teacher(id);
4、创建Teacher实体类
package com.turing.entity; /** * 老师实体类 * @author 大飞哥 * */ public class Teacher { private int id; private String name; ... }
5、修改Student实体类
package com.turing.entity; import org.apache.ibatis.type.Alias; public class Student { private int id; private String sName; private int age; private String email; //关联老师 private Teacher teacher; ... }
6、创建Mapper接口
1)TeacherMapper
public interface TeacherMapper { Teacher findById(int id); }
2)StudentMapper
public interface StudentMapper { Student findById(int id); }
7、创建Mapper映射配置
使用分步查询的方式:
-- 关联查询 -- 分步查询(分步骤查询出来) -- 第一步:先查出1号学生的信息 SELECT * FROM student WHERE id = 1; -- 第二步:再根据学生信息中的老师id,查询老师信息 SELECT * FROM teacher WHERE id = 1;
1)TeacherMapper
2)StudentMapper.xml
8、测试类
1、需求:按id查询学生信息,并且显示老师的名称。
//根据id查询学生 @Test public void testFindById() throws InterruptedException{ //打开会话 SqlSession openSession = sqlSessionFactory.openSession(); //获取mapper StudentMapper mapper = openSession.getMapper(StudentMapper.class); //调用接口方法 Student stu = mapper.findById(1); //输出学生信息 System.out.println(stu.getId()+","+stu.getsName()); //模拟等待3秒 // Thread.sleep(3000); //输出老师信息 // System.out.println(stu.getTeacher().getName()); }
五、配置延迟加载
在上述演示时,我们会发现不管是要学生信息,还是老师信息,都会去查2次,这样会影响查询效率,在MyBatis中,可以设置延迟加载。 延迟加载,也可以成为懒加载或者按需加载。
只需要简单增加两个配置即可:
1、通用配置文件增加:
如果在已有全局配置下,对某个查询不需要延迟加载时,还可以再mapper映射中配置:
fetchType:lazy 延迟加载(默认)
fetchType:eager 立刻加载
2、配置映射文件设置:
一、一对一关联(级联查询)
思路:
级联查询
自定义association
映射配置:
二、一对多(级联查询)
1、实体类
就是在一的一端,增加一个多的一端的属性(该属性通常使用List类型)
public class Teacher { private int id; private String name; private Liststus; ... }
2、接口
public interface TeacherMapper { Teacher findById(int id); }
3、映射配置
三、关联操作
以新增为例
1、新增一名学生信息,并关联以存在的老师。
insert into student values(#{sId}, #{sName}, #{sAge}, #{teacher.tId}) //添加学生,关联以存在老师 @Test public void test03(){ Teacher teacher = new Teacher(); teacher.settId(1); boolean result = studentMapper.insert(new Student("宝莉", 8, teacher)); System.out.println(result); }
2、新增一名学生信息,并关联新的老师。
@Test public void test04(){ try{ //先添加老师,但必须要返回ID //创建老师对象 Teacher teacher = new Teacher();//添加不需要id,因为自动增长 teacher.settName("杨老师"); teacherMapper.insert(teacher); //再添加学生 //创建学生对象 Student student = new Student(); student.setsName("爱美丽");//添加不需要id,因为自动增长 student.setsAge(12); student.setTeacher(teacher); studentMapper.insert(student); }catch (Exception e) { //如果出现异常,则回滚 session.rollback(); } }
映射配置:
insert into teacher values(#{tId},#{tName})
四、缓存机制
1、概念
MyBatis中默认定义了两级缓存。
一级缓存
默认情况下,只有一级缓存开启。一级缓存属于SqlSession级别,也称为本地缓 存。
二级缓存
二级缓存需要手动开启和配置,属于NameSpace级别。
为了提高扩展性,MyBatis定义了缓存接口Cache,可以通过实现Cache接口来定 义二级缓存。
2、一级缓存示例
//根据id查询学生编号,学生姓名,老师姓名 @Test public void testFindById(){ //打开会话 SqlSession openSession = sqlSessionFactory.openSession(); try { //获取Mapper StudentMapper mapper = openSession.getMapper(StudentMapper.class); //调用方法 Student stu = mapper.findById(1); //再查一次 Student stu2 = mapper.findById(1); //输出 System.out.println(stu); System.out.println(stu2); }finally{ openSession.close(); } }
运行结果:对同一个方法调用多次,也只执行了一次查询。
3、一级缓存失效情况
SqlSession不同
SqlSession相同,查询条件不同
SqlSession相同,两次查询之间执行了增删改操作
SqlSession相同,手动清除了一级缓存:session.clearCache();
五、二级缓存(全局缓存)
基于namespace级别的缓存,一个namespace对应一个二级缓存 工作机制
一个会话,查询一条数据,这个数据就会放在当前会话的一级缓存中。
如果会话关闭,一级缓存中的数据会被保留到二级缓存中;新的会话查询 条件相同时,就会去二级缓存中取数据
开启步骤:
实体类要序列化
1、配置步骤:
在通用配置文件中显示开启二级缓存配置:
...
在映射配置文件中配置使用二级缓存:
...
实体类需要序列化
public class Student implements Serializable{ private static final long serialVersionUID = 2371821181824900251L;//自动生成 ... }
测试
//测试二级缓存 @Test public void testFindById3() throws InterruptedException{ //打开会话 SqlSession openSession = sqlSessionFactory.openSession(); SqlSession openSession2 = sqlSessionFactory.openSession(); //获取mapper StudentMapper mapper = openSession.getMapper(StudentMapper.class); StudentMapper mapper2 = openSession2.getMapper(StudentMapper.class); //调用接口方法 Student stu = mapper.findById(1); openSession.close(); Student stu2 = mapper2.findById(1); openSession2.close(); //输出学生信息 System.out.println(stu); System.out.println(stu2); }
执行结果:查询一次 2021-07-14 11:36:36,461 [main] DEBUG - Cache Hit Ratio [com.turing.mapper.StudentMapper]: 0.0 2021-07-14 11:36:36,601 [main] DEBUG - ==> Preparing: select s.s_id,s.s_name,s.s_age,t.t_id,t.t_name from student s inner join teacher t on s.t_id=t.t_id and s.s_id=?
2021-07-14 11:36:36,615 [main] DEBUG - ==> Parameters: 1(Integer) 2021-07-14 11:36:36,626 [main] DEBUG - <== Total: 1 2021-07-14 11:36:36,662 [main] DEBUG - Cache Hit Ratio [com.turing.mapper.StudentMapper]: 0.5 Student [sId=1, sName=张三, sAge=18, teacher=Teacher [tId=1, tName=刘老 师, stus=null]] Student [sId=1, sName=张三, sAge=18, teacher=Teacher [tId=1, tName=刘老 师, stus=null]]
一、动态SQL
MyBatis的动态SQL是基于OGNL表达式的,它可以帮助我们方便的在SQL语句中实现某些逻 辑。 MyBatis中用于实现动态SQL的元素主要有:
if
choose(when,otherwise)
trim(where,set)
foreach
1、if元素
if可以在sql语句中,做非空判断 例如:例如我们在查询员工的时候,如果条件中的字段没有值,则不加入到sql语句中。
2、where
“where”标签会知道如果它包含的标签中有返回值的话,它就插入一 个‘where’。
如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。
在上述if的SQL中有一个问题,如果某个条件不满足,拼接的SQL中会,会在where的后面有一 个and,这样拼接的SQL语句就有问题。 这时,就可以使用
3、set
在写SQL语句中,set后面如果跟了动态SQL,同样会多出一个逗号,我们可以使用
update student where id = #{id} p_name = #{name}, age = #{age}, email = #{email}
4、trim
trim标记是一个格式化的标记,可以完成set或者是where标记的功能。 在上述元素where和set的使用底层其实就是用trim实现的,我们先来看下trim元素中的四个属 性:
prefix:前缀,给整个字符串拼接后的结果加一个指定前缀。
prefixOverrides:前缀覆盖,去掉整个字符串前面指定的字符。
suffix:后缀,给整个字符串拼接后的结果加一个指定的后缀。
suffixOverrides:后缀覆盖,去掉整个字符串后面指定的字符。
1、我们用trim来模拟where元素
2、我们用trim元素模拟set元素
update student where id = #{id} p_name = #{name}, age = #{age}, email = #{email}
5、choose(when,otherwise)
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使 用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句。 例如:只要有一个条件满足,就执行查询,其它条件不管有没有值,都不会执行。
6、foreach
foreach主要用来做循环处理,里面包括6个属性:
collection="ids" --集合
item="id" --从集合中取一个元素
index="" --下标
open="" --拼接前缀
close="" --拼接后缀
separator="" --设置再循环中,用什么分割元素
我们通过案例来学习:
1、批量查询
查询员工id为1,2,7,8,11,15 这个SQL我们可以写成:
SELECT * FROM student WHERE id IN(1,2,7,8,11,15);
那么在动态SQL中可以这样写:
接口:
//根据多个id查询员工 ListfindByIds(@Param("ids")int ...ids);//可变参数,会转换成一个数组
测试类:
//多个id查询 @Test public void test04(){ Listlist = studentMapper.findByIds(1,3,9); System.out.println(list); }
2、批量添加
接口:
接口://添加员工集合 boolean insertList(@Param("stus")Liststus);
测试类:
//测试批量添加学生 @Test public void test05(){ Liststus = new ArrayList (); stus.add(new Student("AAA", 1, new Teacher(1))); stus.add(new Student("BBB", 2, new Teacher(1))); stus.add(new Student("CCC", 3, new Teacher(1))); studentMapper.insertList(stus); }
1)批量添加员工的第一种写法: 这个SQL我们可以写成:
INSERT INTO student VALUES(?,?,?,?),(?,?,?,?),(?,?,?,?);
那么在动态SQL中可以这样写:
insert into student values (#{stu.id},#{stu.name},#{stu.age},#{stu.email})
2)、批量添加员工的第二种写法: 这个SQL我们可以写成:
INSERT INTO student VALUES(?,?,?,?); INSERT INTO student VALUES(?,?,?,?); INSERT INTO student VALUES(?,?,?,?);
那么在动态SQL中可以这样写:
insert into student values(#{stu.id},#{stu.name},#{stu.age},# {stu.email})
注意:这种写法要设置mysql连接器属性:allowMultiQueries=true
url=jdbc:mysql://localhost:3306/java16?allowMultiQueries=true
一、MyBatis Generator安装
在使用mybatis时我们需要重复的去创建pojo类、mapper文件以及dao类并且需要配置它们之间的依赖关系,比较麻烦且做了大量 的重复工作,mybatis官方也发现了这个问题,因此给我们提供了mybatis generator工具来帮我们自动创建pojo类、mapper文件 以及dao类并且会帮我们配置好它们的依赖关系。 但是表连接、存储过程等复杂的SQL还是需要我们手动编写。
1、下载工具
GitHub - mybatis/generator: A code generator for MyBatis.
2、导入jar包
mybatis-generator-core-1.3.7.jar
3、在工程根目录下添加配置文件:mbg.xml
重点只需要关注要修改的地方。
4、执行代码
Listwarnings = new ArrayList (); boolean overwrite = true; File configFile = new File("mbg.xml"); ConfigurationParser cp = new ConfigurationParser(warnings); Configuration config = cp.parseConfiguration(configFile); DefaultShellCallback callback = new DefaultShellCallback(overwrite); MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); myBatisGenerator.generate(null);
二、MyBatis Generator常用方法操作
1、根据id查询
//根据ID查询员工 @Test public void testSelectByPrimaryKey() { Emp emp = mapper.selectByPrimaryKey(6L); System.out.println(emp.getName()); }
2、条件查询,例如:查询id=5,name=小美
要使用条件查询,必须使用Criteria对象,该对象可以拼接很多条件,以andXXX开头。
@Test public void testSelectByExample() { //根据条件查询 EmpExample example = new EmpExample(); Criteria criteria = example.createCriteria(); criteria.andIdEqualTo(5L); criteria.andNameEqualTo("小美"); Listlist = mapper.selectByExample(example); System.out.println(list); }
3、模糊查询,例如:查询name中包含"美"的。
也属于条件查询,使用Criteria对象的andNameLike方法
@Test public void testSelectByExample2() { //根据条件查询 EmpExample example = new EmpExample(); Criteria criteria = example.createCriteria(); criteria.andNameLike("%美%"); Listlist = mapper.selectByExample(example); System.out.println(list); }
4、排序
@Test public void testOrderByClause() { EmpExample example = new EmpExample(); example.setOrderByClause("id DESC"); Listlist = mapper.selectByExample(example); System.out.println(list); }
5、分页查询
MyBatis Generator可以通过插件机制来扩展其功能,其中RowBoundsPlugin是MyBatis Generator中自 带的一个分页插件。可以在MyBatis Generator配置文件generatorConfig.xml中添加这个插件
...
再次运行mvn mybatis-generator:generate生成代码,此时会发现生成的Mapper中会加入一个新的方法: selectByExampleWithRowbounds(XxxExample example, RowBounds rowBounds),可以在代码中调用这个方法来实现分 页:
@Test public void testSelectByExampleWithRowbounds() { int pagerNow = 2; int pagerSize = 3; pagerNow = (pagerNow-1)*pagerSize; EmpExample example = new EmpExample(); RowBounds rowBounds = new RowBounds(pagerNow, pagerSize); Listlist = mapper.selectByExampleWithRowbounds(example, rowBounds); System.out.println(list); }
注意:重复生成Generator,可能会导致映射文件中的内容重复,建议先手动删除已经生成的mapper映射文件,再执行生成。
6、多条件之间“or”拼接
例如:(姓名包含"美",并且id在1~10之间),或者 (部门不在1号部门)。
@Test public void test111() { EmpExample example = new EmpExample(); //先创建一个Criteria对象来设置第一个括号中的条件 Criteria criteria1 = example.createCriteria(); criteria1.andNameLike("%美%").andIdBetween(1L, 10L); //再创建一个Criteria对象来设置第二个括号中的条件 Criteria criteria2 = example.createCriteria(); criteria2.andDeptidNotEqualTo(2); //融合两个example example.or(criteria2); Listlist = mapper.selectByExample(example); System.out.println(list); }
7、添加
@Test public void testInsert() { Emp emp = new Emp(); emp.setName("龙王"); emp.setDeptid(2); mapper.insert(emp); }
8、修改
无非空判断
@Test public void testUpdateByPrimaryKey() { Emp emp = new Emp(); emp.setId(6L); emp.setName("黑人"); emp.setDeptid(2); mapper.updateByPrimaryKey(emp); }
有非空判断:
@Test public void testUpdateByPrimaryKeySelective() { Emp emp = new Emp(); emp.setId(5L); emp.setName("小美美"); mapper.updateByPrimaryKeySelective(emp); }
9、删除
@Test public void testDeleteByPrimaryKey() { mapper.deleteByPrimaryKey(7L); }