本文已参与「新人创作礼」活动,一起开启掘金创作之路。
目录
编辑
编辑
一.介绍
MyBatis是一个基于Java的持久层框架,提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。
持久层:存储在硬盘中,不会断电消失的数据。
SQL Maps:指的是数据库中的数据和Java数据的映射关系。也就是MyBatis封装JDBC的过程。
框架本质就是 jar包+配置文件。
1.MyBatis的特性
1.支持定制化SQL,存储过程以及高级映射的持久层框架
定制化SQL:SQL语句需要自己去写。
存储过程:是MySQL高级学习的内容
普通映射:利用JDBC访问数据库的过程中,自己封装了JDBC工具类,每当把数据从数据库中查询出来后,都会根据反射将它对应Java中的实体类对象,要求是字段名必须跟类的属性名保持一致,否则是映射不了的。
高级映射:实体类中的属性,通过SQL是查询不到的,比如一对多的关系,多对一的关系,这是表和表之间的关系,那么对应的Java中的实体类之间也需要有关系。用SQL查询出来的数据无法对应某个实体或实体的集合。
2.避免手动设置参数和获取结果集
3.可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO映射成数据库中的记录。
sql语句可以写到xml文件中,或写在注解中。
4.是一个半自动的ORM(Object Relation Mapping)框架
JDBC就是手动,所有的过程都要自己去写。
半自动就是封装了部分的功能,像sql还是需要自己写,也可以自己处理映射关系
ORM:对象关系映射,对象指的是Java实体对象,关系指的是关系型数据库,其实就是将Java中的实体类对象和关系型数据库中的数据创建映射关系。
二.初识MyBatis的增删改查
目录结构
编辑
1.预备步骤
Step1:创建maven工程
编辑
Step2:在pom.xml中添加mybatis相关的依赖
<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> </dependencies>
Step3:在resources下面创建mybatis-config.xml文件(核心配置文件)
编辑
核心配置文件详解-配置关于mybatis的相关信息。
在ssm整合后,可以没有这个配置文件,核心配置文件中所配置的内容可以交给spring管理。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入资源文件--> <properties resource="jdbc.properties"/> <!--设置类型别名--> <typeAliases> <!-- 用一个别名User,来代替全类名pojo.User,这个别名不区分大小写 如果不写alias属性,那么就会分配一个默认的别名,这个默认的别名就是类名且不区分大小写 所以写了个User和不写效果一样 --> <!-- <typeAlias type="pojo.User" alias="User"></typeAlias>--> <!-- 以包为单位,给包下所有的类设置默认别名,这里给pojo下面所有的包都设置了默认别名且不区分大小写,这个比较常用--> <package name="pojo"/> </typeAliases> <!-- 配置链接数据库的环境 不管有多少个环境,也只能选择其中一个使用,default表示默认使用哪个环境 --> <environments default="development"> <!-- 配置某个具体的环境每一个environment标签都是一个连接具体数据库的环境 id:表示连接数据库环境的一个唯一标识,不能重复 --> <environment id="development"> <!-- 设置事务管理器的类型,JDBC类型需要手动提交事务 JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,原生指的是 提交就是commit,回滚就是rollback,事务的提交和回滚需要手动处理 MANAGED:被管理,例如被Spring事务 --> <transactionManager type="JDBC"/> <!-- dataSource:配置数据源 type:POOLED 设置数据源的类型,表示使用数据库连接池来缓存数据库连接 type:UNPOOLED 表示不使用数据库连接池来缓存数据库连接 type:JNDI 表示上下文中的数据源 ssm整合之后就不需要设置数据源了, spring提供数据源 --> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!-- 引入映射文件--> <mappers> <!-- resources下面的不叫包(和包的图像不同),是具体的资源,包是装Java类的,所以这里要以路径的方式引入--> <!-- <mapper resource="mappers/UserMapper.xml"/>--> <!-- 以包为单位引入映射文件时需要满足两点 1.mapper接口所在的包要和映射文件所在的包一致,即包名要一样 2.mapper接口要和映射文件的名字一致 --> <package name="mapper"/> </mappers> </configuration>
Step4:创建数据库mybatis和表t_user
编辑
Step5:创建pojo类对应t_user表
属性要和字段名相同,如果字段名有_,那么属性名就用驼峰命名法,查询字段时时用别名查询。
public class User { //保证属性名和字段名一致 private Integer id; private String username; private String password; private Integer age; private String sex; private String email; public User() { } public User(Integer id, String username, String password, Integer age, String sex, String email) { this.id = id; this.username = username; this.password = password; this.age = age; this.sex = sex; this.email = email; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + ''' + ", password='" + password + ''' + ", age=" + age + ", sex='" + sex + ''' + ", email='" + email + ''' + '}'; } }
Step6:创建mapper接口
现在在数据库中有了表,在Java中有了对应的对象。在Java中定义方法去操作表中的数据,就需要mapper来实现。
mapper就是JDBC中的DAO,一个mapper对应一个表。
//当调用接口中的方法,它会自动去匹配一个方法并且执行 public interface UserMapper { /* MyBatis面向接口编程的两个一致 1.mapper接口的映射文件的namespace要和mapper接口的全类名保持一致 2.映射文件中sql语句的id要和mapper接口中的方法名一致 表-实体类-mapper接口-mapper映射文件 一一对应 */ }
Step7:创建mapper的配置文件UserMapper.xml
编辑
就像是mapper接口的实现类,来写具体方法内的sql语句。
<?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="mapper.UserMapper"> </mapper>
Step8:在测试类中通过一系列步骤拿到mapper对象来执行mapper里面的方法
public class MyBatisTest { @Test public void testMyBatis() throws IOException { //加载核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //获取sqlSessionFactoryBuilder SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); //获取sqlSessionFactory对象 SqlSessionFactory factory = builder.build(resourceAsStream); //获取SqlSession,sqlSession代表Java程序和数据库之间的会话。就像HttpSession是Java程序和浏览器之间的会话 //SqlSession默认不自动提交事务,若需要自动提交事务可以在openSession方法中设置为true SqlSession sqlSession = factory.openSession(true);//设置true可以自动提交事务 //获取mapper接口对象,这个方法的底层用到了代理模式,在底层为接口创建了实现类。 UserMapper mapper = sqlSession.getMapper(UserMapper.class); //测试功能,通过mapper调用接口中的方法 }
2.最简单的增删改查操作
1)插入数据
Step1:在UserMapper接口中添加方法
//添加用户信息 int insertUser();
Step2:在UserMapper.xml中添加配置
<!-- int insertUser();--> <insert id="insertUser"> insert into t_user values(null,'admin','123',23,'男','123@qq.com') </insert>
Step3:在测试类中测试
int result = mapper.insertUser(); //sqlSession.commit();//手动提交事务,如果前面openSession中设置了true可以省略该句 System.out.println(result);
2)更新数据
Step1:
//修改用户信息 void updateUser();
Step2:
<!-- void updateUser();--> <update id="updateUser"> update t_user set username = '张三' where id = 12; </update>
3)删除数据
Step1:
//删除用户信息 void deleteUser();
Step2:
<!-- void deleteUser();--> <delete id="deleteUser"> delete from t_user where id=12; </delete>
4)查询数据
Step1:
User getUserById(); //查询所有用户信息 List<User> getAllUser();
Step2:
<!-- User getUserById();--> <!-- resultType:设置默认映射关系,自动将查询出的结果表的字段名来作为属性名来给对象赋值,如果匹配到就赋值,匹配不到就不赋值将查询出来的结果转换成设置好的结果类型,再将结果作为返回值返回到这个方法--> <!-- resultMap:设置自定义的映射关系,处理字段名和属性名不一致的情况,还有一对多和多对一的关系--> <select id="getUserById" resultType="pojo.User"> select * from t_user where id=13; </select> <!-- List<User> getAllUser();--> <select id="getAllUser" resultType="pojo.User"> select * from t_user; </select>
3.功能优化
1.自动提交
int result = mapper.insertUser();
sqlSession.commit();
/*
因为核心配置文件<transactionManager type="JDBC"/>设置为tyep,所以需要手动提交事务
在执行sql后提交
*/
2.日志功能
1.在pom.xml中添加依赖
<!-- 日志信息--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency>
2.在resources下面创建log4j.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"> <param name="Encoding" value="UTF-8" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /> </layout> </appender> <logger name="java.sql"> <level value="debug" /> </logger> <logger name="org.apache.ibatis"> <level value="info" /> </logger> <root> <level value="debug" /> <appender-ref ref="STDOUT" /> </root> </log4j:configuration>
3.测试增删改查功能时会输出日志信息
运行程序的日志信息
编辑
row1:sql信息
row2:参数信息,显示可变参数
row3:受影响的行数
其他
编辑
为什么会越来越详细?
如果DEBUG都需要打印日志信息的话,那么FATAL的提示肯定也要打印信息
三.简化MyBatis的操作
MyBatis中各种标签的添加顺序
The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,
objectWrapperFactory?,reflectorFactory?,plugins?,environments?,
databaseIdProvider?,mappers?)".
1.利用properties文件配置数据库信息
Step1:在resources目录下创建jdbc.properties,存储连接数据库的信息
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis jdbc.username=root jdbc.password=niit
用jdbc.作为前缀是为了表明该键的功能,因为给键取名时是为了见名知意的,往往不同的properties文件的键会重名,用前缀来区分。
Step2:在核心配置文件中添加
<!-- 引入资源文件--> <properties resource="jdbc.properties"/>
<dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource>
2.typeAliases 类型别名
<!--设置类型别名--> <typeAliases> <!-- 用一个别名User,来代替全类名pojo.User,这个别名不区分大小写 如果不写alias属性,那么就会分配一个默认的别名,这个默认的别名就是类名且不区分大小写 所以写了个User和不写效果一样 --> <!-- <typeAlias type="pojo.User" alias="User"></typeAlias>--> <!-- 以包为单位,给包下所有的类设置默认别名,这里给pojo下面所有的包都设置了默认别名且不区分大小写,这个比较常用--> <package name="pojo"/> </typeAliases>
在mapper的配置文件中,查询语句的结果需要返回resultType,如果不设置类型别名,如果想要将数据映射为对象,就需要写类的全类名路径。
<select id="getAllUser" resultType="pojo.User"> select * from t_user; </select>
如果定义了类型别名,就可以只写类名了
<select id="getAllUser" resultType="User"> select * from t_user; </select>
具体的类型别名的用法看上面注释
3.简化引入映射文件
<!-- 引入映射文件--> <mappers> <!-- resources下面的不叫包(和包的图像不同),是具体的资源,包是装Java类的,所以这里要以路径的方式引入--> <!-- <mapper resource="mappers/UserMapper.xml"/>--> <!-- 以包为单位引入映射文件时需要满足两点 1.mapper接口所在的包要和映射文件所在的包一致,即包名要一样 2.mapper接口要和映射文件的名字一致 --> <package name="mapper"/> </mappers>
4.设置核心配置文件的模板和工具类
1.核心配置文件的模板
编辑
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.properties"/> <typeAliases> <package name="pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name="mapper"/> </mappers> </configuration>
2.mapper配置文件的模板
<?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="mapper.UserMapper"> </mapper>
3.获取SqlSession对象工具类
public class SqlSessionUtils { public static SqlSession getSqlSession(){ SqlSession sqlSession = null; try { InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(resourceAsStream); sqlSession = factory.openSession(true); } catch (IOException e) { e.printStackTrace(); } return sqlSession; } }
四.实现动态的增删改查
在Mapper配置文件中获取接口中定义的参数,实现动态的进行增删改查。
1.在配置文件中获取参数的两种方式
方式1:${}
本质是字符串拼接,假如username的值为admin,那么
select * from t_user where username =${username} 对应sql语句为
select * from t_user where username = admin
这样查不到数据,需要查询条件的值为字符串类型,即username="admin"
所以需要这样写
select * from t_user where username ="${username}"
或
select * from t_user where username =‘${username}’
方式2:#{}
本质是占位符赋值,会自动添加引号
select * from t_user where username =#{username} 对应sql语句为
select * from t_user where username = ”admin"
2.根据传递参数的类型个数不同而获取参数的方式不同
1)4中情况
情况1:传递一个参数
User getUserByUsername(String username);
<select id="getUserByUsername" resultType="User"> select * from t_user where username = '${username}' </select> <select id="getUserByUsername" resultType="User"> select * from t_user where username = #{username} </select>
当为一个参数时,括号内的参数名其实是可以随便写的,不写username写aaa也是可以的。
{}里面的内容是什么都不重要,可以任意取名,但最好见名知意
情况2:传递多个参数
User checkLogin(String username,String password);
<!-- User checkLogin(String username,String password);--> <!-- 当底层检测到方法有多个参数时,会自动把这些参数放到一个map集合里面,以arg0/param1,arg1/param2...为键,以参数值为值--> <!--若mapper接口方法的参数有多个时,可以手动将这些参数放到一个map中存储--> <select id="checkLogin" resultType="User"> select * from t_user where username = #{param1} and password = #{arg1} </select>
传递的多个参数会存到map集合中,每个值都对应两个键,比如map集合中第一个值对应的键就对应arg0或者param1.
情况3:传入一个map集合
User checkLoginByMap(Map<String,Object> map);
传入的map集合就替代了mybatis自动创建的map集合,可以从这个map集合中取值。
<select id="checkLoginByMap" resultType="User"> select * from t_user where username = #{username} and password = #{password} </select>
情况4:传入一个对象
int insertUser(User user);
<insert id="insertUser"> insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email}) </insert>
直接通过属性名获取对象中的数据
2)将4中情况转化为两种情况
通过注解的形式,将前三种情况转为一种情况。
User checkLoginByParam(@Param("username") String username, @Param("password") String password);
注解的作用是给键名命名,不用考虑键名根据参数的不同有多种情况了。
3.动态查询
1)普通动态查询
查询一个实体类对象
用对象(User)类型接收
User getUserById(@Param("id") int id);
<!--User getUserById(@Param("id") int id);--> <select id="getUserById" resultType="User"> select * from t_user where id = #{id} </select>
用list集合(List)接收
List<User> getUserById(@Param("id") int id);
<!--User getUserById(@Param("id") int id);--> <select id="getUserById" resultType="User"> select * from t_user where id = #{id} </select>
用map集合接收
查询到的内容就是属性名为键,属性值为值
Map<String,Object> getUserByIdToMap(@Param("id") int id);
<select id="getUserByIdToMap" resultType="map"> select * from t_user where id = #{id} </select>
查询返回一个集合
用list集合接收
List<User> getAllUser();
<select id="getAllUser" resultType="User"> select * from t_user </select>
用map集合接收
每个对象都是一个map集合,用list集合存储所有map集合
List<Map<String,Object>> getAllUserToMap();
<!-- Map<String,Object> getAllUserToMap();--> <select id="getAllUserToMap" resultType="map"> select * from t_user </select>
也可以用注解
//将查询出来的结果的id作为键,每行记录作为一个map集合,一个map集合作为值 //{13={password=123, sex=男, id=13, age=23, email=123@qq.com, username=admin}, 15={password=123, sex=男, id=15, age=30, email=123@qq.com, username=李四}} @MapKey("id") Map<String,Object> getAllUserToMap();
用数据的唯一字段来作为数据的键名
查询返回一个值
//查询用户信息总记录数 int getCount();
<!-- 这个写java.lang.Integer/Integer/integer/int/Int/_int 都可以,既然都可以那就说明这个地方写的是类型别名,是mybatis设置默认的类型别名--> <select id="getCount" resultType="java.lang.Integer"> select count(*) from t_user </select>
分组查询 聚合查询查询结果是单行单列
聚合查询时设置查询结果是mybatis设置的类型别名
编辑
常用的别名
编辑
转换为map集合的好处
查询结果为Map集合
有时页面中所需要的数据并不是从单纯从一个表中查询出来的,所以不能将所查询出来的数据单纯的对应一个类,所以可以放到Map集合中
Map集合响应到浏览器就是一个JSON对象
2)特殊SQL处理
模糊查询
模糊查询
//根据用户名进行模糊查询用户信息 List<User> getUserByLike(@Param("username") String username);
<!--List<User> getUserByLike(@Param("username") String username);--> <select id="getUserByLike" resultType="User"> <!--select * from t_user where username like '%#{username}%'--> <!--'%?%' 此时模糊查询条件为这种,?会被当作字符串中的一部分,而不会当作是占位符,所以不会被赋值--> <!--解决方案1:select * from t_user where username like '%${username}%',--> <!--解决方案2:字符串拼接函数 select * from t_user where username like concat('%',#{username},'%') --> <!--解决方案3:第三种最常用--> select * from t_user where username like "%"#{username}"%" </select>
@Test public void likeTest(){ SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SQLMapper mapper = sqlSession.getMapper(SQLMapper.class); List<User> list = mapper.getUserByLike("李"); System.out.println(list); }
批量删除
比如邮件中,前面有复选框,将复选框全选中就可实现批量删除
//批量删除 int deleteMore(@Param("ids") String ids);
<delete id="deleteMore"> <!--#{}是会自动加单引号的 delete from t_user where id in (‘1,2,3’) 这样是无法执行的,在sql中 in后面的不能加引号 delete from t_user where id in (13,14),当用$时,是这种情况 --> delete from t_user where id in (${ids}) </delete>
@Test public void deleteMoreTest(){ SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SQLMapper mapper = sqlSession.getMapper(SQLMapper.class); int deleteMore = mapper.deleteMore("13,14"); System.out.println(deleteMore); }
用in 关键字,相当于or
批量删除只能用$, 不能用#
动态设置表名
//查询指定表明中的数据 List<User> getUserByTableName(@Param("tableName") String tableName);
<!-- List<User> getUserByTableName(@Param("tableName") String tableName);--> <!-- 要用${},因为表明不能加单引号--> <select id="getUserByTableName" resultType="User"> select * from ${tableName} </select>
@Test public void tableNameTest(){ SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SQLMapper mapper = sqlSession.getMapper(SQLMapper.class); List<User> userByTableName = mapper.getUserByTableName("t_user"); System.out.println(userByTableName); }
数据库中的表的数据太多会影响数据库的性能
将一张表切分,切分成多张表共同存储一张表中的数据
在操作数据时,所操作的表是不一样的,虽然表中存储的数据是同一张表的数据。
操作的表不一样,那么表名需要动态设置。
五.处理复杂关系
数据准备
新建t_employ表
编辑
新建t_dept表
编辑
不管是一对多还是多对一都需要在多的一方设置一的主键,所以要在在员工表中设置部门的did
新建Emp类
public class Emp { private int eid; private String empName; private int age; private String sex; private String email; private int did; }
新建Dept类
public class Dept { private int did; private String deptName; }
1.处理属性名和字段名不同的情况
方式1:起别名
//查询所有员工信息 List<Emp> getAllEmp();
<select id="getAllEmp" resultType="Emp"> select eid,emp_name empName,age,sex,email from t_employ </select>
方式2:通过settings设置mybatis的全局配置
将下划线自动映射为驼峰
<settings> <setting name="mapUnderscoreToCameCase" value="true"/> </settings>
方式3:设置结果映射
resultMap的id要写到select的resultMap属性中
<resultMap id="empResultMap" type="Emp"> <!-- id字段来设置主键字段的映射关系--> <id property="eid" column="eid"></id> <!-- result设置其他普通字段的映射关系--> <!-- 即使字段名和属性名相同 也得写--> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="eamil"></result> </resultMap> <!-- List<Emp> getAllEmp();--> <select id="getAllEmp" resultMap="empResultMap"> select * from t_emp </select>
2.处理多对一关系查询
比如多个员工可以对应一个部门,在查询员工信息时,并显示该员工的部门信息。
员工为主要查询的信息,所以员工表为主表
Step1:在员工表中添加部门对象
public class Emp { private int eid; private String empName; private int age; private String sex; private String email; private int did; private Dept dept; }
Step2:
方式1:级联属性赋值
将查询结果直接一一对应赋值
//查询员工及员 工所在的部门信息 Emp getEmpAndDept(int id);
<!-- Emp getEmpAndDept(int id);--> <resultMap id="empAndDeptResultMap" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <!-- 查询出来的did字段和Emp中的dept属性的did属性映射--> <result property="dept.did" column="did"></result> <result property="dept.deptName" column="dept_name"></result> </resultMap> <select id="getEmpAndDept" resultMap="empAndDeptResultTwo"> select * from t_employ left join t_dept on t_employ.did = t_dept.did where t_employ.eid = #{eid} </select>
方式2:通过association标签,方式2的好处是比方式1清晰
<resultMap id="empAndDeptResultTwo" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <!-- javaType写的是属性的类型--> <!-- association 处理多对一的映射关系 property需要处理的多对映射关系属性名 --> <association property="dept" javaType="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> </association> </resultMap> <select id="getEmpAndDept" resultMap="empAndDeptResultTwo"> select * from t_employ left join t_dept on t_employ.did = t_dept.did where t_employ.eid = #{eid} </select>
方式3:分布查询
先查询员工的信息
Emp getEmpAndDeptByStepOne(@Param("eid") int eid);
<!-- Emp getEmpAndDeptByStepOne(@Param("eid") int eid);--> <resultMap id="empAndDeptByStepResultMap" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <!-- 下面column写的是分步查询的条件 sql语句的唯一标识,接口名.方法名等同于xml中的命名空间.select标签id 得到这个方法的返回结果 并且给dept这个属性赋值 --> <association property="dept" select="mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did" > </association> </resultMap> <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap"> select * from t_employ where eid = #{eid} </select>
property:表示要给dept属性赋值
select:表示将这个方法的查询结果给dept赋值
column:将第一步查询的查询结果的did字段的值传递给select属性中的方法
再查询员工中部门的信息
Dept getEmpAndDeptByStepTwo(@Param("did") int did);
<!-- Dept getEmpAndDeptByStepTwo(@Param("did") int did);--> <select id="getEmpAndDeptByStepTwo" resultType="Dept"> select * from t_dept where did = #{did} </select>
3.处理一对多关系查询
比如一个部门对应多个员工,查询一个部门信息并且显示属于该部门的所有员工信息
Step1:在Dept类中添加List
public class Dept { private int did; private String deptName; private List<Emp> empList; }
Step2:
方式1:通过collection标签
将查询的两张表的结果直接给属性赋值
<!-- Dept getDeptAndEmp(@Param("did") int did);--> <resultMap id="deptAndEmpResultMap" type="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <!-- ofType设置集合中元素的类型--> <collection property="empList" ofType="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> </collection> </resultMap> <select id="getDeptAndEmp" resultMap="deptAndEmpResultMap"> select * from t_dept left join t_employ on t_dept.did = t_employ.did where t_dept.did = #{did} </select>
方式2:分布查询
先查询部门信息
//分布查询第一步:查询某个部门信息 Dept getDeptAndEmpByStepone(@Param("did") int did);
<!-- Dept getDeptAndEmpByStepone();--> <resultMap id="deptAndEmpByStepResultMap" type="dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> <!-- select属性查询的结果为property赋值--> <collection property="empList" select="mapper.EmpMapper.getDeptAndEmpByStepTwo" column="did"> </collection> </resultMap> <select id="getDeptAndEmpByStepone" resultMap="deptAndEmpByStepResultMap"> select * from t_dept where did = #{did} </select>
将第一步查询结果的did传到select的方法中,再将查询结果赋值给属性empList
再查询员工的信息
//分布查询2第二步:查询该部门的员工信息 List<Emp> getDeptAndEmpByStepTwo(@Param("did") int did);
<!-- 分布查询的第二步--> <!-- Emp getDeptAndEmpByStepTwo(@Param("did") int did);--> <select id="getDeptAndEmpByStepTwo" resultType="Emp"> select * from t_employ where did = #{did} </select>
4.延迟加载
延迟加载
分布查询的好处就是延迟加载
编辑
这里说到的全局配置信息就是下划线转驼峰
关联的对象指的是分布查询的第二步,第三步等等
aggressiveLoading开启后,会覆盖掉lazyLoadingEnabled,
要实现延迟加载的话
需要把aggressiveLoading设置为false,默认是false
把lazyLoadingEnabled设置为true,默认是false
编辑
在设置了延迟加载之后,所有的查询都会延迟加载,如果有些功能不需要设置延迟加载,可以在association中设置fetchType来设置是立即加载还是延迟加载
当开启了延迟加载之后,通过fetchType使得延迟加载变得可控,如果没有开启延迟加载,那么这个属性就没有效果。
当设置了延迟加载,那么这个属性默认是lazy。
六.动态SQL
Integer可以赋值null,int不能复制null
1.if标签
编辑
<!-- if条件的使用--> <select id="getEmpByConditionOne" resultType="Emp"> select * from t_employ where 1=1 <!-- 判断,如果不满足条件就不会拼接到sql中 如果第一个条件不成立的话,后面条件成了那么就相当于where后面直接跟了and,导致语法错误 可以在where后面加一个1=1的条件,就可以拼接后面的and条件了 --> <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> <if test="email != null and email != ''"> and email = #{email} </if> </select>
2.where标签
编辑
<!-- where标签--> <!-- List<Emp> getEmpByCondition(Emp emp);--> <select id="getEmpByConditionTwo" resultType="Emp"> select * from t_employ <!-- 用了where标签的话,如果where标签中有if标签成立 能够自动生成where关键字,还能把内容前多余的and去掉余的or也能去掉 注意where标签不能将内容后面的and或or去掉,只能去掉内容前面的 当所有的if标签都不成立,那么就不会生成关键字 --> <where> <if test="empName != null and empName != ''"> emp_name = #{empName} </if> <if test="age != null and age != ''"> and age = #{age} </if> <if test="sex != null and sex != ''"> and sex = #{sex} </if> <if test="email != null and email != ''"> and email = #{email} </if> </where> </select>
3.trim标签
编辑
编辑
<!--trim标签--> <!-- prefix/suffix:将trim标签中内容前面或后面添加指定内容 suffixOverrides/prefixOverrides:将trim标签中内容前面或后面去掉指定内容 --> <select id="getEmpByConditionThree" resultType="Emp"> select * from t_employ <!--若标签中有内容时也就是有if标签生效时,会把最后面的指定内容去掉 and和or都生效 若标签中没有内容时也就是if标签都不生效时,trim标签没有任何效果,所以就不会加前缀 --> <trim prefix="where" suffix="" prefixOverrides="and|or" suffixOverrides=""> <if test="empName != null and empName != ''"> emp_name = #{empName} and </if> <if test="age != null and age != ''"> and age = #{age} and </if> <if test="sex != null and sex != ''"> and sex = #{sex} and </if> <if test="email != null and email != ''"> and email = #{email} </if> </trim> </select>
4.choose when otherwise标签
<!-- choose when otherwise 相当于 if...else if... else,只要有一个成立其他的就不再判断了--> <!-- List<Emp> getEmpByChooseWhenOtherwise(Emp emp);--> <!-- 如果when标签中有一个满足条件,就加上,如果when标签都不满足,则加上otherwise标签的条件 when相当于 if或者else if 至少有一个 otherwise相当于else,至多有一个 --> <select id="getEmpByChooseWhenOtherwise" resultType="Emp"> select * from t_employ <where> <choose> <when test="empName != null and empName != ''"> emp_name = #{empName} </when> <when test="age != null and age != ''"> age = #{age} </when> <when test="sex != null and sex != ''"> sex = #{sex} </when> <when test="email != null and email != ''"> email = #{email} </when> <otherwise> did = 1 </otherwise> </choose> </where> </select>
编辑
第一个条件成立,剩下的条件就不判断了
5.foreach标签
<!--注意分隔符前面和后面默认会加上空格,所以用or作连接时不用担心空格的问题--> <delete id="deleteMoreByArray"> delete from t_employ where <foreach collection="eids" item="eid" separator="or"> eid = #{eid} </foreach> </delete> <!-- int deleteMoreByArray(Integer[] eids);--> <delete id="deleteMoreByArrayOne"> delete from t_employ where eid in <!--item表示数组中的每个元素,separator设置循环体中每一次循环的分隔符 open表示循环的内容以什么开始 close表示循环的内容以什么结束 ( <foreach collection="eids" item="eid" separator="," close="" open=""> #{eid} </foreach> ) --> <foreach collection="eids" item="eid" separator="," close=")" open="("> #{eid} </foreach> </delete>
//通过数组实现批量删除 int deleteMoreByArray(@Param("eids") Integer[] eids); //通过list集合实现批量添加 int insertMoreByList(@Param("emps") List<Emp> emps);
实现批量添加和批量删除
通过数组实现批量删除,通过集合实现批量添加。
6.sql标签定义字段名
<!-- 避免在select后面写太多的字段名,通过sql标签定义常用的字段名通过include引用sql片段--> <sql id="empColums">eid,emp_name,age,sex,email</sql> <select id="getEmpByCondition" resultType="Emp"> select <include refid="empColums"></include> from t_employ </select>
七.缓存
缓存:会把查询出来的数据进行记录,下次查询相同数据的时候就会从缓存中去拿,不会重新访问数据库了。缓存只对查询功能有效。
1.一级缓存
编辑
通过同一个SqlSession查询出来的数据会被缓存,再次通过这个SqlSession查询数据就会从缓存中取。
编辑
编辑
观察执行的sql,第二个语句没有执行sql说明是从缓存中拿到的数据
编辑
同一个sqlSession 获取的mapper
编辑
不同sqlSession获取的mapper
一级缓存的范围是同一个SqlSession
编辑
两个查询之间有一次插入操作,则一级缓存失效。
编辑
清空缓存
2.二级缓存
编辑
关闭或提交之后:sqlSession有两个方法:commit和close方法
在没有提交或者关闭sqlSession时,查询的数据会保存到一级缓存中,如果开启了二级缓存,那么提交或关闭sqlSession后,查询的数据会保存到二级缓存中
实现序列化接口
编辑
手动清空缓存只对一级缓存有效,因为clearCache方法只在SqlSession中
二级缓存的相关配置
在xml中设置cache标签的属性
编辑
缓存是缓存到内存中的
缓存回收策略是为了节省内存
刷新间隔:二级缓存多长时间刷新一次,clearCache只会清空一级缓存
缓存仅仅调用语句时刷新指的是调用增删改的语句时。
读写缓存时,返回给用户缓存对象的副本,对这个副本进行修改不会影响缓存中的数据
MyBatis缓存查询的顺序
编辑
整合第三方缓存EHCache
由其他的技术代替二级缓存
编辑
日志门面 相当于里面提供的接口
门面的一个具体实现就是接口的一个具体实现
编辑
编辑
编辑
编辑
编辑
编辑
八.MyBatis的逆向工程
编辑
逆向工程有两个版本
清新简洁版 只有增删改查五个方法
奢华尊享版中,带有example的方法,都是根据条件的执行操作
1.启动逆向工程
Step1:配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>MyBatis_MBG</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>16</maven.compiler.source> <maven.compiler.target>16</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!-- 日志信息--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.0</version> <!--插件的依赖--> <dependencies> <!-- 逆向工程的核心依赖--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <dependency> <!-- 数据库连接池--> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.2</version> </dependency> <!-- MySQL驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> </dependencies> </plugin> </plugins> </build> </project>
Step2: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> <properties resource="jdbc.properties"/> <typeAliases> <package name="pojo"/> </typeAliases> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name="mapper"/> </mappers> </configuration>
Step3:逆向工程配置文件,名字必须为generatorConfig,xml
<?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> <!-- targetRuntime:执行生成的逆向工程的版本 MyBatis3Simple:生成基本的CRUD(清新简洁版) MyBatis3:生成带条件的CRUD(奢华尊享版 )--> <context id="DB2Tables" targetRuntime="MyBatis3"> <!--数据库连接的信息:驱动类、连接地址、用户名、密码 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="niit" /> <!-- Javabean生成策略--> <javaModelGenerator targetPackage="pojo" targetProject=".\src\main\java" > <!-- 是否能够使用此包,如果写true,那么targetPackage中每个点都对应一层包 如com.mybatis.pojo 是false的话就认为com.mybatis.pojo是`一个`目录--> <property name="enableSubPackages" value="true"/> <!-- 是否去掉开头和结尾的空格,字段名前后有空格的话会自动去掉--> <property name="trimStrings" value="true"/> </javaModelGenerator> <!--SQL映射文件生成策略--> <sqlMapGenerator targetPackage="mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!--Mapper接口的生成策略--> <javaClientGenerator targetPackage="mapper" targetProject=".\src\main\java" type="XMLMAPPER"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 逆向分析表--> <table tableName="t_employ" domainObjectName="Emp" /> <table tableName="t_dept" domainObjectName="Dept"/> </context> </generatorConfiguration>
Step4:在数据库中创建对应的表
Step5:双击插件创建逆向工程的mapper,pojo
编辑
九.其他功能
1.获取自增的主键
useGeneratedKeys:设置当前标签中的sql使用了自增的id
keyProperty:将自增的主键的值返回,因为insert delete等操作的返回值只能是受影响
的行数,所以要想返回主键的值,只能将这个主键的值赋值到对象的某个属性中。
因为传进来的是一个对象。
void insertUser(User user);
<!-- keyProperty 表示把自动递增的主键放到对象的id属性中 --> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email}) </insert>
@Test public void testWay5() throws IOException { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class); User user = mapper.checkLoginByParam("admin", "123"); System.out.println(user); }
2. 分页插件
编辑
编辑
编辑
PageInfo{pageNum=1, pageSize=1, size=1, startRow=1, endRow=1, total=5, pages=5, list=Page{count=true, pageNum=1, pageSize=1, startRow=0, endRow=1, total=5, pages=5, reasonable=false, pageSizeZero=false}[Emp{eid=1, empName='张三', age=16, sex='男', email='123@qq.com', did=1}], prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=5, navigatepageNums=[1, 2, 3, 4, 5]}
pageSize:指定每页显示的条数
size:当前页显示的条数,如最后一页可能显示不到指定数量
prePage:上一页
nextPage:下一页
isFirstPage:是否是上一页
isLastPage:是否是下一页
hasPreviousPage:是否有上一页
hasNextPage:是否有下一页
navigatePages:导航分页
navigatepageNums:根据navigatePages计算出显示的页数,将正在显示的页数放到正中间。