Loading... # MyBatis框架 > 官方文档地址: https://mybatis.org/ ## 什么是框架 框架类似于脚手架,建筑时候帮助你更快的完成建造任务,单纯的框架不能用来当房子用,但是辅助盖楼也是很有必要的,能够节省更多的时间。另一种说法就是框架提供了一系列的模板代码,只需要很少的代码来实现基础功能,最早时候通过JDBC连接数据库,每次都写加载驱动,连接数据库,写SQL语句,执行语句,处理结果集,关闭连接,多麻烦呀,所以框架就提供了一个入口,写上驱动名和相关的参数就能进行SQL操作了。 我还有一层理解,就是框架中集成了大量的代码,通过反射等各种方式,对性能有些影响,但是对于后期维护起来更加方便,所以这个问题能难为到我这种强迫症,毕竟现在都是框架横行的时代,不用也不行啊,所以权衡性能和维护难度吧。【这里是我的疑问,大佬可以帮我解答以下】 ## 软件的三层架构 1. 数据持久化存储层:主要和数据库打交道的 2. 业务层:主要实现业务功能的 3. 表现层:与用户进行交互的,并且能与业务层联系 DAO层与业务层联系,业务层与表现层联系,这样的架构应该是目前主流的规范。 ## MVC设计思想 最早我对三层构架和MVC模型总是混淆,MVC是一种设计思想,他们确实有相似的地方,Model 是模型,模型拥有多种处理任务,模型可以对多个视图提供数据,可以有效的减少代码量。View是视图,与前端页面关联最大,一般都是由网页或者是客户端界面组成。Controller是控制器,控制器接收用户请求,并且完成对模型的指向, ## MyBatis入门 MyBatis是一个持久层框架,提供了与数据库交互的方法,能够减少大量代码,主要写一些SQL语句就可以了,通过XML配置,这样维护起来也更加方便了,与代码混淆在一起就会感到很臃肿。 ## MyBatis依赖 MyBatis虽然可以操作数据库,但是也需要MySQL的驱动支持,所以在使用MyBatis时,仍要导入MySQL的驱动,以Maven项目为例,依赖如下所示: ```xml <dependencies> <!--mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> </dependencies> ``` ## MyBatis核心配置 MyBatis需要一个主配置文件,这个文件中包含了与数据库连接的配置以及映射文件的配置,所以核心配置文件不能丢掉。 ```xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <!--可以设置多个环境,选择的时development--> <environment id="development"> <!--development环境--> <transactionManager type="JDBC"/> <!--事务处理方式为JDBC--> <dataSource type="POOLED"> <!--通过池连接--> <property name="driver" value="com.mysql.jdbc.Driver"/> <!--MySQL驱动全限定名--> <property name="url" value="jdbc:mysql:///db_mybatis"/> <!--数据库链接地址--> <property name="username" value="root"/><!--用户名--> <property name="password" value="root"/><!--密码--> </dataSource> </environment> </environments> <mappers> <!--映射文件配置--> <!--单一文件--> <mapper resource="UserMapper.xml"/> <!--扫描包下的映射文件--> <package name="com.alc.mapper"/> <!--当你没有写xml,使用的时实现类,用这个--> <mapper class="com.alc.DoctorMapper"/> </mappers> </configuration> ``` ## 核心配置文件标签 在资源目录创建一个数据库连接的配置文件:文件名(db.properties) ```properties db.drvierName=com.mysql.jdbc.Driver db.url=jdbc:mysql:///db db.username=root db.password=root ``` 核心配置文件中读取上面的配置文件 ```xml <properties resource="db.properties"></properties> <!--读取上面配置文件--> <dataSource type="POOLED"> <!--property name值为固定的, value:可以根据需求自定义--> <!--获取值的方法也和el表达式类似--> <property name="driver" value="${db.drvierName}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </dataSource> ``` ## 映射文件 ```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"> <mapper namespace="userMapper"> <!--代表命名空间,目前理解成一个类就行了,后期与接口对接,直接写个接口不用写实现类就能执行SQL语句了--> <select id="selectUser" resultType="com.alc.entity.User"> <!-- select 选择标签, id为唯一标识,与接口名对应 resultType为返回值类型--> select * from user </select> <!--insert:添加标签;parameterType:参数类型--> <insert id="addUser" parameterType="com.alc.entity.User"> <!--参数类型为User,也就是User中的变量名,一定要有get方法嗷--> insert into user values(#{id},#{name},#{age},#{sex},#{birthday}) </insert> </mapper> ``` ## 类型的别名 就像上面的resultType写的都是全限定名,每次这么写太累了,所以可以给他们起个别名。在核心配置文件中,添加别名标签和属性。如下所示: ```xml <typeAliases> <!--type:类的全限定名;alias:别名--> <typeAlias type="com.alc.entity.User" alias="u"/> </typeAliases> ``` 这样就可以在映射文件中直接使用u来代表我的User类了。 ```xml <select id="selectById" resultType="u" parameterType="java.lang.Integer"> select * from user where id=#{id} </select> ``` ## 迫不及待的运行 maven提供了测试类,我们可以在测试类中写代码,看一下是否能够执行呢?[当前没有使用接口方式,只有核心配置文件、映射文件和实体类] ```java @Test public void query() throws IOException { //alt+enter:补全返回值类型 //读取mybatis的核心配置文件 InputStream stream = Resources.getResourceAsStream("mybatisConfig.xml"); //获取SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream); //获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //args:映射文件中的namespace.操作标签的id值 List<User> list = sqlSession.selectList("userMapper.selectUser"); //输出结果 list.forEach(System.out::print); //关闭连接对象 sqlSession.close(); } @Test public void add() throws IOException { //读取mybatis的核心配置文件 InputStream stream = Resources.getResourceAsStream("mybatisConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream); SqlSession sqlSession = sqlSessionFactory.openSession(); User u=new User(); u.setName("Matrix"); u.setAge(23); u.setSex("男"); u.setBirthday(LocalDate.now()); //args1:映射文件中的namespace.操作标签的id值;args2:保存的对象 sqlSession.insert("userMapper.addUser",u); //提交事务 sqlSession.commit(); sqlSession.close(); } ``` ## 小任务 如果多次操作数据库,读取配置文件,创建SqlSessionFactory代码也挺繁琐的,可不可以提取一个工具类出来呢? ## CURD ### 标签 1. SELECT 2. UPDATE 3. INSERT 4. DELETE 也就是标签名为这些,这些标识告诉MyBatis你要执行的操作是什么,从而对结果集进行处理,例如: ```xml <mapper namespace="userMapper"> <!--select:查询标签; id:唯一标识; resultType:返回结果类型--> <select id="selectUser" resultType="com.alc.entity.User"> /*sql语句*/ select * from user </select> <!--insert:添加标签;parameterType:参数类型--> <insert id="addUser" parameterType="com.alc.entity.User"> insert into user values(#{id},#{name},#{age},#{sex},#{birthday}) </insert> <!--update:更新--> <update id="updateUser" parameterType="com.alc.entity.User"> update user set name=#{name},age=#{age},sex=#{sex},birthday=#{birthday} where id=#{id} </update> <!--delete:删除--> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id=#{id} </delete> <!--根据主键查询某一条数据--> <select id="selectById" resultType="com.alc.entity.User" parameterType="java.lang.Integer"> select * from user where id=#{id} </select> </mapper> ``` ## 基于接口开发 基于接口开发,不需要写实现类,直接写xml就可以了,减少了代码量,但是也有他的规范,规范如下。 1. 映射文件中的namespace属性值==接口类的全限定名 2. 映射文件中CRUD标签中的id属性与接口类方法名一致 3. 映射文件中的参数类型和返回值类型要与方法一致 ## MyBatis-API + Resources 通过调用此方法可以读取到核心配置文件 + Resources.getResourceAsStream("myBatisConfig.xml") + SqlSessionFactory 通过工厂对象创建连接对象 + SqlSessionFactory.openSession(); + SqlSession 进行语句执行的实例方法 + SqlSession.* * * 理解:核心配置文件相当于资金,拿着钱买了个工厂,工厂的参数就是钱,现在由工厂了就开启流水线,流水线相当于一个产品(SqlSession),然后让产品去做他该做的事。 ## ResultMap 当实体类和数据库中字段不匹配时,需要添加对应法则,告诉MyBatis那个列对应的时那个变量。 ```XML <resultMap id="userInfo" type="u"> <!--result:结果; property:实体类中的属性名;column:查询的字段名--> <result property="sname" column="name"/> </resultMap> <select id="selectById" resultMap="userInfo"> select * from user where id=#{id} </select> ``` ## 多参数查询 通常情况下,我们的查询语句都附加了条件,有些可能是多个,比如登录,我们有一个实体类User,其中有两个参数,一个是username,另一个是password, 这样我们可以封装到实体类中进行sql查询,(user.username user.password)这样的方式,不难看出,这样的方式只有一个参数,这个参数是实体对象,但是如果查询参数不具有实体类或者是不想封装,也可以通过多种参数的方式进行查询。 我们开启这个项目,需要导入依赖文件,并且配置MyBatis的核心配置文件,数据库部分就不过多介绍了,官方网站上有中文解释,文章前也有相关的模板。在这里呢,我们在映射标签中需要定义mappers,因为项目中很少有一个映射,所以建议试用package进行扫描,这样就不需要后期的一个一个的添加了,也避免了忘记添加映射而无法正常运行。 ```xml <mappers> <package name="com.alc.mapper"/> </mappers> ``` 例如: 根据姓名和年龄查询用户信息;这时候,我们分析一下我们需要那些参数和返回值类型。返回值类型是一个List列表,所以接口需要以List进行定义,而参数有多种方法定义。如下所示: ```java //1.根据性别与年龄查询用户信息 List<User>selectBySexAndAge(String sex,Integer age); //2.根据性别与年龄查询用户信息 List<User>selectBySexAndAge(User user); //3.根据性别与年龄查询用户信息 List<User>selectBySexAndAge(Map<String,Object>map); //4.根据性别与年龄查询用户信息 List<User>selectBySexAndAge(@Param("sex") String sex, @Param("age") Integer age); ``` 本文就以这样的接口定义,这里给出了四种方式,路径为com.alc.mapper.UserMapper,这里只提供了映射接口,但是需要映射文件,也就是xml文件,需要在resources文件中添加相同的路径,(这里于IDEA 的编译方式有关,如果可以编译java文件夹下的非编译文件,就可以放在java文件夹下了,为了规范,我们需要在resources文件夹下定义)。 > 目录定义如下所示嗷 >-java >|-com >|-alc >|-mapper > |-UserMapper.java > |-UserMapper.xml # 如果这样,需要添加编译目录,不符合规范,不建议这么做 >... >-resources >|-com >|-alc >|-mapper > |-UserMapper.xml # 注意 目录的定义要与映射java文件的目录一致。。。 然后我们就到了关键的映射定义部分了。 ```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"> <mapper namespace="com.alc.mapper.UserMapper"> <select id="selectBySexAndAge1" resultType="com.alc.entity.User"> <!-- arg0 表示第一个参数 param1 表示第一个参数 以此类推 --> SELECT * FROM user <where> sex = #{arg0} and age = #{param2} </where> </select> <select id="selectBySexAndAge2" resultType="com.alc.entity.User"> SELECT * FROM user <where> sex = #{sex} and age = #{age} </where> </select> <select id="selectBySexAndAge3" resultType="com.alc.entity.User"> SELECT * FROM user <where> sex = #{sex} and age = #{age} </where> </select> <select id="selectBySexAndAge4" resultType="com.alc.entity.User"> SELECT * FROM user <where> sex = #{sex} and age = #{age} </where> </select> </mapper> ``` 测试类如下所示:建议对上面的代码进行截图,以贴图的方式,进行对比,这样更容易理解这些代码了。 ```java import com.alc.entity.User; import com.alc.mapper.UserMapper; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; import java.util.Map; public class MyBatisTest { private static UserMapper mapper = null; static { try { InputStream stream = Resources.getResourceAsStream("mybatisConfig.xml"); SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream); SqlSession session = build.openSession(); mapper = session.getMapper(UserMapper.class); } catch (IOException e) { e.printStackTrace(); } } // 整合以下模板代码,测试的时候直接试用就好了。 @Test public void T1(){ List<User> rst = mapper.selectBySexAndAge1("男", 38); //直接通过参数索引获取,所以不需要特殊处理。 rst.forEach(System.out::println); System.out.println("T1"); } @Test public void T2(){ User user = new User(); user.setSex("男");user.setAge(38); //因为封装到实体对象中了,所以需要传递一个对象过去,具体内容存放在在该对象中 List<User> rst = mapper.selectBySexAndAge2(user); rst.forEach(System.out::println); System.out.println("T2"); } @Test public void T3(){ Map params = new HashMap(); params.put("sex","女");params.put("age",28); //以map集合的方式存储查询参数,我们都知道,map集合的key是唯一的,所以mybatis会解析map,对其中的参数进行查询 List rst = mapper.selectBySexAndAge3(params); rst.forEach(System.out::println); System.out.println("T3"); } @Test public void T4(){ List<User> rst = mapper.selectBySexAndAge4("女", 28); //通过别名去取,也就是接口中的@Param中的值,不妨改变以下试试看。 rst.forEach(System.out::println); System.out.println("T4"); } } ``` > ```properties > ------------------------------------------------------- > T E S T S > ------------------------------------------------------- > Running MyBatisTest > User(id=1, name=����, age=38, sex=��, birthday=Wed Nov 11 11:12:13 CST 2020) > T1 > User(id=1, name=����, age=38, sex=��, birthday=Wed Nov 11 11:12:13 CST 2020) > T2 > User(id=2, name=ij��ʦ, age=28, sex=Ů, birthday=Mon Dec 21 14:32:12 CST 2020) > T3 > User(id=2, name=ij��ʦ, age=28, sex=Ů, birthday=Mon Dec 21 14:32:12 CST 2020) > T4 > Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.633 sec > > Results : > > Tests run: 4, Failures: 0, Errors: 0, Skipped: 0 > ``` > > 已经通过了。 ## 模糊查询 映射xml中需要做调整,我们经过上面的代码不难看出来,mybatis也就是进行字符串的拼接,但是有没有发现,我们对参数的传入都是通过‘#’这个符号进行拼接的,在这里引入一个知识点,'#' 这个符号是对sql语句进行预编译的,MyBatis将会转义其中的字符,如果使用'$'符号,将会当作字符串来处理,不会进行预编译。 ```xml <select id="selectLikeName" resultType="com.alc.entity.User"> select * from user where name like #{name} </select> <!--这种方式需要在java代码中手动添加'%' 不是很方便,但是可以解决模糊查询的问题--> <select id="selectLikeName" resultType="com.alc.entity.User"> select * from user where name like "%"#{name}"%" </select> <!--这种方式看起来很奇怪,但是mysql可以使用,因为没有用过Oracle,不知道Oracle是否支持这种方式--> <select id="selectLikeName" resultType="com.alc.entity.User"> select * from user where name like '%${name}%' </select><!--进行sql语句的拼接,存在sql注入问题--> <select id="selectLikeName" resultType="com.alc.entity.User"> select * from user where name like CONCAT(CONCAT('%',#{name}),'%') </select><!--这种方式是使用了SQL中的字符串拼接函数,CONCAT(AC,DB)==>"ACBD"--> ``` ## Log4j 在SQL执行过程中,我们可以通过日志对执行过程进行查看,这里可以使用log4j来实现这个操作 + 添加依赖 ```xml <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>2.0.0-alpha1</version> </dependency> ``` + log4j.properties 配置 ```properties log4j.rootLogger=info,stdout,D ### 输出到控制台 ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE}%5p%c{1}:%L-%m%n ### 输出到日志文件 ### log4j.appender.D=org.apache.log4j.DailyRollingFileAppender log4j.appender.D.File=D:/logs/log.log log4j.appender.D.Append=true log4j.appender.D.Threshold=DEBUG log4j.appender.D.layout=org.apache.log4j.PatternLayout log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}[ %t:%r ]-[%p]%m%n ``` :warning: 文件名不能变,只能是log4j.properties + 配置mybatis核心配置文件 (添加这条配置) ```xml <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> ``` ## 获取添加数据信息 想象一个场景,在高并发的情况下,多人操作数据库,我们向用户表中插入一条数据,由于ID是自动生成,我们如果需要这个ID,进行其他表中的关联操作,这就需要在执行完成后对ID进行回取。 以上一个场景为例,我们需要在xml中进行设置,把并且id返回给我们的java程序,代码如下所示。 ```xml <!--useGeneratedKeys:true:支持在添加数据的时候,获取注解的值; keyProperty:将查询到主键值赋值给某一个属性 --> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user VALUE(NULL,#{name},#{password},#{sex},#{age},#{birthday}) </insert> ``` 通过上面的代码我们可以看到,insert添加了两个属性,并且缺少了parameterType属性,parameterType这个属性一般情况是可以省略不写的,但是为了后期维护的便捷以及见名知意,还是写上比较好,mybatis会自动识别,但是为了规范以及代码的可读性,还是写上比较合理。 看到这里,我们还没有获取到id值,实际上这里只是对于配置的书写,然而获取仍需要在java代码中实现,比如我们要向用户表中添加一条数据。 ```java User u = new User(); //定义一个用户实体对象 u.setName("老崔"); u.setSex("男"); u.setBirthday(new Date()); u.setPassword("123"); u.setAge(34); //上面我们没有对ID进行赋值。 Integer rst = mapper.insertUser(u); // 调用添加接口映射 System.out.println(rst + "," + u.getId()); //rst为1代表插入成功,id为刚刚添加数据的id值,切记,前提是id字段自动增长 session.commit();//最后不要忘记提交嗷。 ``` 根据日志文件,我们分析一下。 ```bash 2021-03-28T11:22:15.793612Z 11 Connect study@localhost on study using TCP/IP 2021-03-28T11:22:15.798292Z 11 Query /* mysql-connector-java-5.1.49 ( Revision: ad86f36e100e104cd926c6b81c8cab9565750116 ) */SELECT @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@query_cache_size AS query_cache_size, @@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout 2021-03-28T11:22:15.820716Z 11 Query SET character_set_results = NULL 2021-03-28T11:22:15.821021Z 11 Query SET autocommit=1 2021-03-28T11:22:15.825516Z 11 Query SET autocommit=0 # 默认是手动提交(手动事务管理) 2021-03-28T11:22:15.842043Z 11 Query select @@session.transaction_read_only 2021-03-28T11:22:15.842416Z 11 Query INSERT INTO user VALUE(NULL,'老崔','123','男',34,'2021-03-28 19:22:15.57') # 添加 2021-03-28T11:22:15.846378Z 11 Query commit ``` 在上面没有进行执行 SELECT LAST_INSERT_ID(); 这条语句。还有另一种方法,但是代码量稍微大一点,并且我个人认为不太严谨,因此就仅以介绍,不做详解。 ```xml <!-- keyProperty:查询到的主键值赋值给哪一个属性名; keyColumn:查询的主键对应的表中的字段名 resultType:查询的主键的数据类型 order: BEFORE/AFTER在进行添加动作之前还是之后进行查询 --> <insert id="add" parameterType="u"> <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER"> select LAST_INSERT_ID() </selectKey> insert into user values(#{id},#{name},#{password},#{age},#{sex},#{birthday}) </insert> ``` ## 再精简一点 当我们进行查询或者其他操作的时候,还是需要一些模板SQL语句,当然,有些时候参数也是模板代码,所以可以使用sql标签。 ```xml <sql id="sel"> SELECT * FROM user </sql> ``` 但是我们定义完了,如何去使用呢? ```xml <****(select) id="***"> <include refid="sel"/> where *** </****(select)> ``` ## 高级映射 表与表的关联关系 - 多对一 - 一对一 - 一对多 - 多对多 多对一和一对多,我的理解是相对的,例如老师与学生之间的关系,一个老师教多个学生,多个学生被一个老师教,一对多,多对一,是参照关系的不同,因此不要对高级映射关系有畏惧之心。 ### 多对一 以学生作为"参照物",一个学生对应多个老师,因此再定义学生类的时候,需要把老师定义在学生中,数据表中,学生中要包含老师的id值,这样就存在了对应关系了。 映射文件的配置(方式一): 学生映射 ```xml <resultMap id="stuResult" type="com.alc.entity.Student"> <id property="id" column="sid"/> <result property="name" column="sname"></result> <result property="sex" column="ssex"></result> <result property="age" column="sage"></result> <!--association:多对一/一对一的配置 javaType:设置当前这个属性的java类型--> <association property="teacher" javaType="com.alc.entity.Teacher"> <id property="id" column="tid"/> <result property="name" column="tname"></result> <result property="sex" column="tsex"></result> <result property="age" column="tage"></result> </association> </resultMap> <select id="findById" resultMap="stuResult"> SELECT s.id sid, s.NAME sname, s.sex ssex, s.age sage, t.id tid, t.NAME tname, t.sex tsex, t.age tage FROM student s, teacher t WHERE s.teacher_fk = t.id AND s.id = #{id} </select> ``` 方式二: 学生映射 ```xml <resultMap id="stuResult" type="com.alc.entity.Student"> <!--多对一配置 association:多对一关联配置 property:实体类中起关联作用的属性名 column:查询到的数据库表中起关联作用的字段名 select:调用在别的mapper中定义的查询: namespace.id --> <association property="teacher" column="teacher_fk" select="com.alc.mapper.TeacherMapper.findById"/> </resultMap> <select id="findById" resultMap="stuResult"> select * from student where id=#{id} </select> ``` 老师映射 ```xml <mapper namespace="com.alc.mapper.TeacherMapper"> <select id="findById" resultType="com.alc.entity.Teacher"> select * from teacher where id=#{id} </select> </mapper> ``` 相比来说,两种方式都可以实现相同的作用,但是明显后者SQL语句比较少,前者但是更能体现出其中的关系。 ### 一对多 根据上面的多对一,我们依然可以通过老师与学生之间的关系产生一对多的模型,一个老师对应多个学生,表结构不需要改变,但是在实体定义的时候,老师中的学生需要通过一个列表的方式存储,也就是说,在老师的实体中,包含其所教的所有学生信息。 这样的方式,是以老师为参照物的。 实现方式一:(老师映射) ```xml <resultMap id="teacherResult" type="com.alc.one2many.Teacher"> <id property="id" column="tid"/> <result property="name" column="tname"></result> <result property="sex" column="tsex"></result> <result property="age" column="tage"></result> <!-- collection:对多配置 ofType:集合中存放的元素数据类型 --> <collection property="students" ofType="com.alc.one2many.Student"> <id property="id" column="sid"/> <result property="name" column="sname"></result> <result property="sex" column="ssex"></result> <result property="age" column="sage"></result> </collection> </resultMap> <select id="findById" resultMap="teacherResult"> SELECT s.id sid, s.NAME sname, s.sex ssex, s.age sage, t.id tid, t.NAME tname, t.sex tsex, t.age tage FROM student s, teacher t WHERE s.teacher_fk = t.id AND t.id = #{id} </select> ``` 实现方式二:(老师映射) ```xml <resultMap id="teacherResult" type="com.alc.one2many.Teacher"> <!--select:调用StudentMapper中定义的findByTeacherId方法--> <collection property="students" column="id" select="com.alc.dao.StudentMapper.findByTeacherId"></collection> </resultMap> <select id="findById" resultMap="teacherResult"> select * from teacher where id=#{id} </select> ``` 学生映射 ```xml <mapper namespace="com.alc.dao.StudentMapper"> <select id="findByTeacherId" resultType="com.alc.one2many.Student"> select * from student where teacher_fk=#{id} </select> </mapper> ``` ### 多对多 这层概念有些混乱,但是,简单的说,多对多就是一对多和多对一的叠加版,以商城系统为例,一个订单可以对应多个商品,订单详情中包含订单和商品信息,一个商品可以存在于多个订单中,一个订单也可以包含多个商品。 实际中用的不是很多,其实我很不喜欢这样的复杂查询,我认为DAO层就应该是一些简单的SQL查询,其余的由服务层处理。 ## 动态SQL - if 标签 - 属性:test - 值:条件 - > ```xml > <if test="name !=null and name !=''"> > where name like #{name} > </if> > ``` - choose标签 - 属性:无 - 子标签 :when - 属性:test - 值:条件 - > ```xml > <when test="sex!=null and sex!=''"> > sex=#{sex} > </when> > <when test="age!=null"> > age>#{age} > </when> > <otherwise> <!--相当于switch中的default--> > 1=1 > </otherwise> > ``` - where 标签 - 属性:无 - 子标签:无 - 作用:自动填充where - > ```xml > select * from user > <where> > <if test="name !=null and name !=''"> > name like #{name} > </if> > </where> > ``` > > 如果测试条件成立,则添加where > > 如果不成立,就不添加where - set标签 - 属性:无 - 子标签:无 - 作用:在更新时根据字段添加逗号,不需要手动添加, - > ```xml > update user > <set> > <if test="name!=null"> > name=#{name} > </if> > <if test="age!=null"> > age=#{age} > </if> > <if test="sex!=null"> > sex=#{Sex} > </if> > </set> > where id=#{id} > ``` - foreach标签 - 遍历传入的参数,生成长语句,类似于批量操作 - 属性:collection - 值:array 表示传入的是数组 - 值:collection 表示传入的是集合(Map) - 属性:item - 值:遍历过程中单一元素存放的变量 - 属性:open - 值:遍历前添加的字串 - 属性:close - 值:遍历后添加的字串 - 属性:separator - 值:每次遍历中间分割的符号 - > ```xml > <insert id="batchInsert"> > /*批量数据添加*/ > insert into user values > <foreach collection="collection" item="user" open="(" close=")" separator="),("> > #{user.id},#{user.name},#{user.age},#{user.sex},#{user.birthday} > </foreach> > </insert> > ``` ## 注解形式 不建议使用这种方式,毕竟java文件太长的话,维护起来还是比较麻烦,并且编译在class中。 方法就是在映射接口中添加注解标签,语句放在注解里,如下所示。 ```JAVA @Select("SELECT * FROM user WHERE id = #{id}") User findById(int id); ``` 相应的也有Update Delete Insert标签。 当进行多表查询时,还需要借助另一个标签Results,其中分为一对多和多对一两种场景。接下来分别演示。 ### 一对多 学生映射文件: ```java //根据教师id查询学生信息 @Select("select * from student where teacher_fk=#{id}") List<Student>selectByTeacherId(int id); ``` 教师映射文件: ```java //根据教师的id查询教师信息及其对应的学生信息 @Select("select * from teacher where id=#{id}") @Results(value={ @Result(id = true,property = "id",column = "id"),//@Result(id = true)(表示这个值是表中的主键) @Result(property ="name" ,column ="name"), //<result property="" column=""></result> @Result(property ="sex" ,column ="sex"), @Result(property ="age" ,column ="age"), @Result(property = "students",column = "id", many =@Many(select = "com.alc.mapper.StudentMapper.selectByTeacherId"),fetchType=FetchType.LAZY) }) Teacher selectById(int id); ``` ### 多对一 学生映射文件: ```java //根据教师id查询学生信息 @Select("select * from student where id=#{id}") @Results(value={ @Result(id = true,property = "id",column = "id"), @Result(property ="name" ,column ="name"), @Result(property ="sex" ,column ="sex"), @Result(property ="age" ,column ="age"), @Result(property = "teacher",column = "id", one =@One(select = "com.alc.mapper.TeacherMapper.selectById"),fetchType=FetchType.LAZY) }) Student QueryById(int id); ``` 教师映射文件: ```java //根据教师的id查询教师信息及其对应的学生信息 @Select("select * from teacher where id=#{id}") Teacher selectById(int id); ``` ## 一级缓存 默认情况下,一级缓存是打开的,当进行查询操作时,将会把结果存放在缓存中,当进行增删改操作,会清空缓存中的数据,避免总是查询数据库,降低系统效率。实际上,缓存中的数据存放在sqlSession对象中。 ## 二级缓存 二级缓存默认关闭,如果开启,需要在核心配置文件中的settings标签中定义cacheEnabled为true,还需要在映射文件中添加<cache/>标签,二级缓存通常存放一些不经常改变的数据。通俗的说,他们作用域不太一样,一级缓存在sqlsession对象中,而二级缓存对当前的映射生效。当某一条语句不希望使用缓存,则在映射标签中添加属性useCache为false。 ## 延迟加载(懒加载) 当进行多表查询时,有些情况下时不需要即使查询第二张表,如果数据量较大,就会降低用户体验,当用户需要第二张表数据时,再进行查询。 应用场景:用户实体中存在该用户的部门信息,但是页面中只有在查看详情以及编辑时需要,这个时候就可以使用懒加载策略。 ```XML <resultMap id="UserResult" type="com.alc.one2many.User"> <collection property="department" column="did" select="com.alc.dao.DeptMapper.findByDid" fetchType="lazy"> </collection> </resultMap> <select id="findById" resultMap="UserResult"> select * from user where id=#{id} </select> <!--fetchType:默认的情况下是eager,如果设置为lazy,就会起到延迟记载的效果--> ``` ## 逆向工程 MyBatis提供了逆向工程,逆向工程可以通过数据库的连接,自动生成实体类、映射文件、以及相应的实现方法。 以下是实现逆向的依赖文件 ```xml <dependencies> <!--逆向工程需要的jar--> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.7</version> </dependency> <!--mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--junit测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> ``` 以下是配置文件 ```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> <context id="DB2Tables" targetRuntime="MyBatis3"> <!--实体类实现序列化接口--> <plugin type="org.mybatis.generator.plugins.SerializablePlugin" /> <!--生成实体类中的toString方法--> <plugin type="org.mybatis.generator.plugins.ToStringPlugin" /> <commentGenerator> <!--关闭注释--> <property name="suppressAllComments" value="true"></property> </commentGenerator> <!-- 设定数据库连接 --> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/db_mybatis" userId="root" password="root"> </jdbcConnection> <!-- 生成 实体类 存放的位置 --> <javaModelGenerator targetPackage="com.alc.entity" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- 生成的映射文件的位置 --> <sqlMapGenerator targetPackage="com.alc.mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- 生成的接口的存放位置 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.alc.mapper" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 设定反向生成的表 --> <table tableName="goods"></table> <table tableName="student"></table> <table tableName="teacher"></table> <table tableName="user"></table> </context> </generatorConfiguration> ``` 以下是测试文件 ```java @Test public void generator() { try { List<String> warnings = new ArrayList<String>(); boolean overwrite = true; File configFile = new File("配置文件路径"); 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); } catch (Exception e) { e.printStackTrace(); } } ``` ## 异常 如果爆BindingException异常,检查下面的配置是否正确。 1. 接口文件名和映射文件名是否一致 2. 不需要写实现类,按照基于接口开发的规则 3. 映射文件路径和java接口路径是否一致 4. 是否在resources下创建的文件夹,不要习惯性的以'.'创建层级结构,资源文件中不能以‘.’作为目录分割 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏