以下代码获取地址:
https://github.com/Jenyow/codelib/tree/master/codelib-parent/codelib-springboot-samples/codelib-springboot-sample-mybatis
对于代码中的不足,或者其他实现需要补充的,可以提出,我们共同探讨探讨
MyBatis Generator 是一个可以生成 MyBatis 代码的工具。(当然不用也是可以的,只要你不怕麻烦( ╯▽╰))
以下是基于 Maven 的 MyBatis Generator 配置:
POM.xml:
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.5version>
<configuration>
<verbose>trueverbose>
<overwrite>falseoverwrite>
configuration>
<dependencies>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.42version>
dependency>
dependencies>
plugin>
plugins>
build>
必须加入数据库驱动,我用的是 mysql ,所以加入如下依赖。
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
generatorConfig.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db_dbtest
username=root
password=******
generatorConfig.xml:
<generatorConfiguration>
<properties resource="generatorConfig.properties"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin" />
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
<plugin type="org.mybatis.generator.plugins.CaseInsensitiveLikePlugin" />
<plugin type="org.mybatis.generator.plugins.ToStringPlugin" />
<commentGenerator>
<property name="suppressDate" value="true" />
<property name="suppressAllComments" value="flase" />
commentGenerator>
<jdbcConnection driverClass="${driver}" connectionURL="${url}" userId="${username}" password="${password}">
jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
javaTypeResolver>
<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>
<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 的尾部。但是值得注意的是,如果修改了代码生成的部分,再执行命令,修改的部分将会被覆盖掉。
影响这一行为的配置是。建议不要在注释里生成时间戳信息。
对于新增的表,可以在 pom.xml 中指定本次执行 mybatis-generator:generate 命令生成表的代码,多个表名用逗号隔开。
MyBatis Generator 给每个表生成一个实体类、Example条件类、Mapper接口、xml映射。
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})
// 需要加事务,防止各用例间相互影响
@Transactional
public class TextbookMapperTest {
@Autowired
private TextbookMapper textbookMapper;
@Before
public void setUp() throws Exception {
}
@Test
public 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 配置中关键是
<insert id="insert" parameterType="com.codelib.springboot.sample.mybatis.pojo.Textbook">
<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>
查询:
关键是
<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);
}
对于关联查询,如果存在同名的字段,需要给字段取别名,以区分开来
关键在于
Course.java:
...
// 一对一
private Textbook textbook;
// 一对多
private List 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 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 映射及配置很有帮助。对于一对一、一对多、多对多的用法,关键是思维的转换,先理解清楚一对一关系的用法,另外两个就迎刃而解了。
源码是最好的文档
欢迎指出不足