MyBatis 从入门到熟悉.md
来源:互联网 发布:nba历年总决赛数据 编辑:程序博客网 时间:2024/06/05 20:30
MyBatis从入门到熟悉
- MyBatis从入门到熟悉
- MyBatis Generator
- MyBatis
- 测试
- 一对一
- 一对多
- 多对多
- 总结
- 参考
以下代码获取地址:
https://github.com/Jenyow/codelib/tree/master/codelib-parent/codelib-springboot-samples/codelib-springboot-sample-mybatis
对于代码中的不足,或者其他实现需要补充的,可以提出,我们共同探讨探讨
MyBatis Generator
MyBatis Generator 是一个可以生成 MyBatis 代码的工具。(当然不用也是可以的,只要你不怕麻烦( ╯▽╰))
以下是基于 Maven 的 MyBatis Generator 配置:
POM.xml:
<build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <configuration> <!--执行过程会输出到控制台 --> <verbose>true</verbose> <!--不允许覆盖生成的文件 --> <!-- 如果设置为true,如果生成的java文件存在已经同名的文件,新生成的文件会覆盖原有的文件。 这个过程大概可以理解为:删除原来的,重新生成新的.对于自己写的、修改的内容没有了 如果设置为false,如果存在同名的文件,MBG会给新生成的代码文件生成一个唯一的名字 例如: MyClass.java.1, MyClass.java.2 等等 --> <overwrite>false</overwrite> <!-- 如果指定了该参数,逗号隔开的这个表会被运行, 这些表名必须和 <table> 配置中的表面完全一致。 只有指定的这些表会被执行。 如果没有指定该参数,所有的表都会被执行。 --> <!-- 对于修改、新增的表,指定该属性进行构建比较合理 --> <!-- <tableNames></tableNames> --> </configuration> <dependencies> <!-- 配置这个依赖主要是为了等下在配置MG的时候可以不用配置classPathEntry这样的一个属性,避免代码的耦合度太高 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.42</version><!-- 必须指定版本号 --> </dependency> </dependencies> </plugin> </plugins> </build>
必须加入数据库驱动,我用的是 mysql ,所以加入如下依赖。
<!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
generatorConfig.properties:
driver=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/db_dbtestusername=rootpassword=******
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> <properties resource="generatorConfig.properties"/> <!-- 元素类型为 "context" 的内容必须匹配 (注意顺序) "(property*,plugin*,commentGenerator?,(connectionFactory|jdbcConnection), javaTypeResolver?,javaModelGenerator,sqlMapGenerator?,javaClientGenerator?,table+)"。 --> <context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 这个插件给由MBG生成的Java模型对象增加了equals和hashCode方法 --> <plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" /> <!-- 这个插件给由MBG生成的Javas添加了java.io.Serializable标记接口。这个插件给实体类增加了serialVersionUID字段。 --> <plugin type="org.mybatis.generator.plugins.SerializablePlugin" /> <!-- 该插件给Example类添加方法(实际上是给Criteria内部类)来支持不区分大小写的LIKE搜索 --> <plugin type="org.mybatis.generator.plugins.CaseInsensitiveLikePlugin" /> <!-- 该插件给实体类添加toString()方法。 --> <plugin type="org.mybatis.generator.plugins.ToStringPlugin" /> <commentGenerator> <!-- 设置为flase,生成注释包含时间时间戳 --> <property name="suppressDate" value="true" /> <!-- 用来指定MBG生成的代码中是否包含任何注释 设置为false,运行多次插件,不会生成重复的内容 在pom.xml插件配置中,配置不重写文件,所以不需要插件生成的注释 --> <property name="suppressAllComments" value="flase" /> </commentGenerator> <!-- 元素定义如何连接目标数据库 --> <jdbcConnection driverClass="${driver}" connectionURL="${url}" userId="${username}" password="${password}"> </jdbcConnection> <!-- targetProject:被假定为一个已存在的目录结构。 如果目录结构不存在MBG将会失败. targetPackage:将会转换为 targetProject 适当的子目录结构。 如果有必要,MBG会创建这些子目录。 enableSubPackages:Java模型生成器应该使用子包。 这意味着在这种情况下生成的模型对象将被放置在名为 test.model.db2admin 的包中(因为表在 DB2ADMIN schema中)。 如果 enableSubPackages 属性设置为 false, 那么包名将会是 test.model。 Java模型生成器也应该对字符串进行trim操作。 这意味着任何字符串属性的setter方法将调用trim方法 - 如果您的数据库可能会在字符末尾返回空白符,这是非常有用的。 --> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!-- 元素来指定生成 Java 模型对象所属的包 --> <javaModelGenerator targetPackage="com.codelib.springboot.sample.mybatis.pojo" targetProject="src/main/java"> <property name="constructorBased" value="true" /> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- 元素来指定生成 SQL 映射文件所属的包和的目标项目 --> <!-- 如果目标是MyBatis3,那么只有当您选择javaClientGenerator需要XML时,他才是 <context> 元素的一个必须的子元素。 --> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- 元素来指定目标包和目标项目生成的客户端接口和类 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.codelib.springboot.sample.mybatis.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 配置表映射 --> <table tableName="courses" domainObjectName="Course"> <property name="constructorBased" value="true" /> <property name="ignoreQualifiersAtRuntime" value="true" /> </table> <table tableName="students" domainObjectName="Student"> <property name="constructorBased" value="true" /> <property name="ignoreQualifiersAtRuntime" value="true" /> </table> <table tableName="textbooks" domainObjectName="Textbook"> <property name="constructorBased" value="true" /> <property name="ignoreQualifiersAtRuntime" value="true" /> </table> <table tableName="grades" domainObjectName="Grade"> <property name="constructorBased" value="true" /> <property name="ignoreQualifiersAtRuntime" value="true" /> </table> <table tableName="student_courses" domainObjectName="StudentCourse"> <property name="constructorBased" value="true" /> <property name="ignoreQualifiersAtRuntime" value="true" /> </table> </context></generatorConfiguration>
我配置的是生成 xml 的映射。
基本配置可以复用,只需更改对应的包名和表信息即可。
配置好之后,通过 maven 执行 mybatis-generator:generate 命令,即可生成相应的代码。
对于上面的配置,如果执行两次 mybatis-generator:generate 命令,java 的代码会生成备份,xml的不会被覆盖,在xml 中新增的部分会被移到 xml 的尾部。但是值得注意的是,如果修改了代码生成的部分,再执行命令,修改的部分将会被覆盖掉。
影响这一行为的配置是 <commentGenerator> 。建议不要在注释里生成时间戳信息。
对于新增的表,可以在 pom.xml 中 <tableNames></tableNames> 指定本次执行 mybatis-generator:generate 命令生成表的代码,多个表名用逗号隔开。
MyBatis Generator 给每个表生成一个实体类、Example条件类、Mapper接口、xml映射。
MyBatis
MyBatis 配置:
import java.util.Properties;import org.apache.ibatis.plugin.Interceptor;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import org.springframework.transaction.annotation.EnableTransactionManagement;import com.alibaba.druid.pool.DruidDataSource;import com.github.pagehelper.PageInterceptor;@Configuration@EnableTransactionManagement@MapperScan(value = "com.codelib.springboot.sample.mybatis.mapper")public class MyBatisConfig { @Autowired private DruidDataSource dataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); // mybatis分页 PageInterceptor pageHelper = new PageInterceptor(); Properties props = new Properties(); props.setProperty("reasonable", "true"); props.setProperty("supportMethodsArguments", "true"); props.setProperty("returnPageInfo", "check"); props.setProperty("params", "count=countSql"); pageHelper.setProperties(props); // 添加插件 Interceptor[] plugins = new Interceptor[] { pageHelper }; sqlSessionFactoryBean.setPlugins(plugins); PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); sqlSessionFactoryBean .setMapperLocations(resolver.getResources("classpath:mapper/*.xml")); // 在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 sqlSessionFactoryBean.setTypeAliasesPackage("com.codelib.springboot.sample.mybatis.pojo"); return sqlSessionFactoryBean.getObject(); }}
java 配置和 xml 的配置是对应的。
MyBatis xml 映射配置文件详解,可以通过网址( http://www.mybatis.org/mybatis-3/zh/configuration.html)了解学习
查看 SqlSessionFactoryBean 源码,不难发现跟 xml 对应的 set 方法。
别名包的配置,感觉尤为有用,可以在 mapper xml 配置中省略对象的包名,可以使 xml 更加清晰简洁。
对于不声明别名的情况下,默认是类名(首字母小写)
测试
我的是配置内存数据库 H2 进行测试。
在 src/text/resources 下创建两个文件:application.yml、init_table.sql
application.yml:
spring.datasource: type: com.alibaba.druid.pool.DruidDataSource name: mybatistest driverClassName: org.h2.Driver url: jdbc:h2:mem:db_users;MODE=MYSQL;INIT=RUNSCRIPT FROM './src/test/resources/init_table.sql' username: password:
init_table.sql:
-- ------------------------------ Table structure for grades-- ----------------------------DROP TABLE IF EXISTS `grades`;CREATE TABLE `grades` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '年级ID', `name` varchar(255) NOT NULL COMMENT '年级名', PRIMARY KEY (`id`));-- ------------------------------ Records of grades-- ----------------------------INSERT INTO `grades` VALUES ('1', '大一');INSERT INTO `grades` VALUES ('2', '大二');INSERT INTO `grades` VALUES ('3', '大三');-- ------------------------------ Table structure for students-- ----------------------------DROP TABLE IF EXISTS `students`;CREATE TABLE `students` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '学生ID', `name` varchar(255) NOT NULL COMMENT '学生名', PRIMARY KEY (`id`));-- ------------------------------ Records of students-- ----------------------------INSERT INTO `students` VALUES ('1', '赵一');INSERT INTO `students` VALUES ('2', '钱二');INSERT INTO `students` VALUES ('3', '孙三');INSERT INTO `students` VALUES ('4', '李四');INSERT INTO `students` VALUES ('5', '王五');-- ------------------------------ Table structure for textbooks-- ----------------------------DROP TABLE IF EXISTS `textbooks`;CREATE TABLE `textbooks` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '教科书ID', `name` varchar(255) NOT NULL COMMENT '教科书名', PRIMARY KEY (`id`));-- ------------------------------ Records of textbooks-- ----------------------------INSERT INTO `textbooks` VALUES ('1', '《高等数学》');INSERT INTO `textbooks` VALUES ('2', '《Java编程基础》');INSERT INTO `textbooks` VALUES ('3', '《设计模式》');INSERT INTO `textbooks` VALUES ('4', '《大学英语I》');-- ------------------------------ Table structure for courses-- ----------------------------DROP TABLE IF EXISTS `courses`;CREATE TABLE `courses` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '课程ID', `name` varchar(255) NOT NULL COMMENT '课程名', `textbook_id` int(11) NOT NULL COMMENT '教科书ID', `grade_id` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `textbook_id` (`textbook_id`), KEY `grade_id` (`grade_id`), CONSTRAINT `courses_ibfk_3` FOREIGN KEY (`textbook_id`) REFERENCES `textbooks` (`id`), CONSTRAINT `courses_ibfk_4` FOREIGN KEY (`grade_id`) REFERENCES `grades` (`id`));-- ------------------------------ Records of courses-- ----------------------------INSERT INTO `courses` VALUES ('1', '高等数学', '1', '1');INSERT INTO `courses` VALUES ('2', '大学英语I', '4', '1');INSERT INTO `courses` VALUES ('3', 'JAVA入门', '2', '2');INSERT INTO `courses` VALUES ('4', '设计模式', '3', '3');-- ------------------------------ Table structure for student_courses-- ----------------------------DROP TABLE IF EXISTS `student_courses`;CREATE TABLE `student_courses` ( `course_id` int(11) NOT NULL COMMENT '课程ID', `student_id` int(11) NOT NULL COMMENT '学生ID', PRIMARY KEY (`course_id`,`student_id`), KEY `student_id` (`student_id`), CONSTRAINT `student_courses_ibfk_1` FOREIGN KEY (`student_id`) REFERENCES `students` (`id`), CONSTRAINT `student_courses_ibfk_2` FOREIGN KEY (`course_id`) REFERENCES `courses` (`id`));-- ------------------------------ Records of student_courses-- ----------------------------INSERT INTO `student_courses` VALUES ('1', '1');INSERT INTO `student_courses` VALUES ('2', '1');INSERT INTO `student_courses` VALUES ('3', '1');INSERT INTO `student_courses` VALUES ('1', '2');INSERT INTO `student_courses` VALUES ('2', '2');INSERT INTO `student_courses` VALUES ('3', '2');INSERT INTO `student_courses` VALUES ('1', '3');INSERT INTO `student_courses` VALUES ('2', '3');INSERT INTO `student_courses` VALUES ('3', '3');
测试类例子,DruidDataSourceConfig 是我项目中 druid 数据库连接池的配置类:
import static org.junit.Assert.*;import org.junit.Before;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringRunner;import org.springframework.transaction.annotation.Transactional;import com.codelib.springboot.sample.mybatis.config.DruidDataSourceConfig;import com.codelib.springboot.sample.mybatis.config.MyBatisConfig;import com.codelib.springboot.sample.mybatis.pojo.Textbook;@RunWith(SpringRunner.class)@SpringBootTest(classes={MyBatisConfig.class, DruidDataSourceConfig.class})// 需要加事务,防止各用例间相互影响@Transactionalpublic class TextbookMapperTest {@Autowiredprivate TextbookMapper textbookMapper;@Beforepublic void setUp() throws Exception {}@Testpublic void testSelectByPrimaryKey() {Textbook textbook = textbookMapper.selectByPrimaryKey(1);String expected = "《高等数学》";String actual = textbook.getName();assertEquals(expected, actual);}}
对于关系型数据库,除了单表操作外,表间关系一般可以归结为三种情况:一对一、一对多和多对多。
一对一
课程 Course 和教科书 Textbook 是一对一的关系
插入:
/** * 一对一插入 * 当依赖的Textbook不存在时,需要先将数据插入Textbook,然后再插入Course * @param course * @return */ @Override public int intsertCourseTextbook(Course course) { int result = 0; try { Textbook textbook = course.getTextbook(); textbookMapper.insert(textbook); // 关键在于 textbook 插入后获取 id course.setTextbookId(textbook.getId()); courseMapper.insert(course); result = 1; } catch (Exception e) { logger.error("插入失败:{}", e.toString()); } return result; }
xml 配置中关键是 <selectKey> 的配置,插入后返回 id :
<insert id="insert" parameterType="com.codelib.springboot.sample.mybatis.pojo.Textbook"> <!-- WARNING - @mbg.generated This element is automatically generated by MyBatis Generator, do not modify. --> <selectKey keyProperty="id" resultType="int" order="AFTER"> SELECT LAST_INSERT_ID() AS ID </selectKey> insert into textbooks (id, name) values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}) </insert>
查询:
关键是 <association> 标签的配置
<resultMap id="CourseTextbookResultMap" type="course"> <id column="id" javaType="java.lang.Integer" jdbcType="INTEGER" property="id" /> <result column="name" javaType="java.lang.String" jdbcType="VARCHAR" property="name" /> <result column="grade_id" javaType="java.lang.Integer" jdbcType="INTEGER" property="gradeId" /> <result column="textbook_id" javaType="java.lang.Integer" jdbcType="INTEGER" property="textbookId" /> <association column="textbook_id" javaType="textbook" property="textbook" resultMap="TextbookResult" /> </resultMap><resultMap id="TextbookResult" type="textbook"> <id column="textbook_id" javaType="java.lang.Integer" jdbcType="INTEGER" property="id" /> <result column="textbook_name" javaType="java.lang.String" jdbcType="VARCHAR" property="name" /> </resultMap><select id="selectCourseTextbookResultMapByPrimaryKey" parameterType="java.lang.Integer" resultMap="CourseTextbookResultMap"> select a.id, a.name, a.grade_id, a.textbook_id, b.id textbook_id, b.name textbook_name from courses a left join textbooks b on b.id=a.textbook_id where a.id=#{id,jdbcType=INTEGER} </select>
@Test public void testSelectCourseTextbookResultMapByPrimaryKey() { Course course = coursemapper.selectCourseTextbookResultMapByPrimaryKey(1); String expected = "《高等数学》"; String actual = course.getTextbook().getName(); assertEquals(expected, actual); }
对于关联查询,如果存在同名的字段,需要给字段取别名,以区分开来
一对多
关键在于 <collection> 标签的配置,多的一则是用 List 。
Course.java:
...// 一对一private Textbook textbook;// 一对多private List<Student> students = new ArrayList<>();...
<resultMap id="CourseStudentsResultMap" type="course"> <id column="id" javaType="java.lang.Integer" jdbcType="INTEGER" property="id" /> <result column="name" javaType="java.lang.String" jdbcType="VARCHAR" property="name" /> <result column="grade_id" javaType="java.lang.Integer" jdbcType="INTEGER" property="gradeId" /> <result column="textbook_id" javaType="java.lang.Integer" jdbcType="INTEGER" property="textbookId" /> <collection ofType="student" property="students" resultMap="StudentResult" /> </resultMap><resultMap id="StudentResult" type="student"> <id column="student_id" javaType="java.lang.Integer" jdbcType="INTEGER" property="id" /> <result column="student_name" javaType="java.lang.String" jdbcType="VARCHAR" property="name" /> </resultMap><select id="selectCourseStudentsResultMapByPrimaryKey" parameterType="int" resultMap="CourseStudentsResultMap"> select a.id, a.name, a.grade_id, a.textbook_id, c.id student_id, c.name student_name from courses a left join student_courses b on a.id=b.course_id left join students c on b.student_id=c.id where a.id = #{id,jdbcType=INTEGER} </select>
@Test public void selectCourseStudentsResultMapByPrimaryKey() { Course course = coursemapper.selectCourseStudentsResultMapByPrimaryKey(1); int actual = course.getStudents().size(); int expected = 3; assertEquals(expected, actual); }
多对多
这一点跟 Hibernate 不太一样,需要为关联表建实体类。其它的就跟一对多差不多了,只需要将一对多的思想转换一下即可。
/** * 多对多 * 对于 Student 和 Coureses 都已经存在,只是建立关联关系的情况 * 只需要往 student_courses 表插入数据即可 */ @Override public int insertStudentCourses(Student student) { int result = 0; try { int studentId = student.getId(); List<Course> courses = student.getCourses(); for (Course course : courses) { StudentCourseKey studentCourseKey = new StudentCourseKey(course.getId(), studentId); studentCourseMapper.insert(studentCourseKey); result ++; } } catch (Exception e) { logger.error("插入失败:{}", e.toString()); } return result; }
总结
对于 MyBatis 的使用关键是 sql 的编写。先从单表开始,了解 Example 类的用法,对理解 xml 映射及配置很有帮助。对于一对一、一对多、多对多的用法,关键是思维的转换,先理解清楚一对一关系的用法,另外两个就迎刃而解了。
源码是最好的文档
欢迎指出不足
参考
- MyBatis Generator:http://www.mybatis.org/generator/
- MyBatis:http://www.mybatis.org/mybatis-3/zh/index.html
- MyBatis 从入门到熟悉.md
- Mybatis从入门到熟悉(1)
- Markdown--从入门到精通(md源文件)
- Lucene从入门到熟悉(二)检索
- Lucene从入门到熟悉(三)分词
- EntityFrameWork 从入门到熟悉(1)---DbFirst
- (转)从入门到熟悉HTTPS
- 九个问题从入门到熟悉HTTPS
- EntityFrameWork 从入门到熟悉(3)-CodeFirst
- -九个问题从入门到熟悉HTTPS
- SpringBoot学习:从入门到熟悉
- 九个问题从入门到熟悉HTTPS
- log4j 整理 从入门到熟悉到使用
- mybatis 从入门到精通
- mybatis从入门到精通
- mybatis从入门到精通
- mybatis 从入门到总结
- Lucene从入门到熟悉(一)概念&建立索引
- 【CodeForces
- 【java基础】注解(Annotation)
- 论述:为什么选择PM
- POJ
- Saver
- MyBatis 从入门到熟悉.md
- 封装
- 欢迎使用CSDN-markdown编辑器
- lua if else end
- Socket 绑定异常 解决方案
- 如何在32位程序中突破地址空间限制使用超过4G的内存
- log日志输出用处用法等
- [py]tornado2-通过cookie保护页面
- 进程创建过程详解 CreateProcess