mybatis学习总结:annotation与xml结合示例

5 篇文章 0 订阅

简介

  在之前的文章里讨论过mybatis纯xml或者annotation的开发。同时,也讨论了针对不同对象关系情况下的实现细节。在实际的开发应用中,我们会发现,有时候单纯的使用某一种方式来开发的话并不一定有最佳的效率。比如说当我们使用纯xml的时候,会发现里面有很多比较繁琐的配置,而且因为很多sql语句因为是写在xml配置文件里,一个是容易出错,另外对于一些特殊符号还要做一些处理,这样就显得开发的效率不理想。但是使用annotation的话,如果想要重用一些元素比如ResultMap的话就会比较麻烦,每次要重复定义一些元素。所以,如果能够结合两者一些比较好的地方,对于开发来说会更加理想。

 

示例

SQL脚本

  示例对应的SQL脚本如下:

CREATE TABLE ADDRESSES 
(
  ADDR_ID INT(11) NOT NULL AUTO_INCREMENT,
  STREET VARCHAR(50) NOT NULL,
  CITY VARCHAR(50) NOT NULL,
  STATE VARCHAR(50) NOT NULL,
  ZIP VARCHAR(10) DEFAULT NULL,
  COUNTRY VARCHAR(50) NOT NULL,
  PRIMARY KEY (ADDR_ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;

CREATE TABLE STUDENTS 
(
  STUD_ID INT(11) NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(50) NOT NULL,
  EMAIL VARCHAR(50) NOT NULL,
  PHONE VARCHAR(15) DEFAULT NULL,  
  DOB DATE DEFAULT NULL,
  GENDER VARCHAR(6) DEFAULT NULL, 
  BIO LONGTEXT DEFAULT NULL,
  PIC BLOB DEFAULT NULL,
  ADDR_ID INT(11) DEFAULT NULL,  
  PRIMARY KEY (STUD_ID),
  UNIQUE KEY UK_EMAIL (EMAIL),
  CONSTRAINT FK_STUDENTS_ADDR FOREIGN KEY (ADDR_ID) REFERENCES ADDRESSES (ADDR_ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;

CREATE TABLE TUTORS 
(
  TUTOR_ID INT(11) NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(50) NOT NULL,
  EMAIL VARCHAR(50) NOT NULL,
  PHONE VARCHAR(15) DEFAULT NULL,  
  DOB DATE DEFAULT NULL,
  GENDER VARCHAR(6) DEFAULT NULL,
  BIO LONGTEXT DEFAULT NULL,
  PIC BLOB DEFAULT NULL,
  ADDR_ID INT(11) DEFAULT NULL,
  PRIMARY KEY (TUTOR_ID),
  UNIQUE KEY UK_EMAIL (EMAIL),
  CONSTRAINT FK_TUTORS_ADDR FOREIGN KEY (ADDR_ID) REFERENCES ADDRESSES (ADDR_ID)  
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;


CREATE TABLE COURSES 
(
  COURSE_ID INT(11) NOT NULL AUTO_INCREMENT,
  NAME VARCHAR(100) NOT NULL,
  DESCRIPTION VARCHAR(512) DEFAULT NULL,
  START_DATE DATE DEFAULT NULL,
  END_DATE DATE DEFAULT NULL,
  TUTOR_ID INT(11) NOT NULL,
  PRIMARY KEY (COURSE_ID),
  CONSTRAINT FK_COURSE_TUTOR FOREIGN KEY (TUTOR_ID) REFERENCES TUTORS (TUTOR_ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;


CREATE TABLE COURSE_ENROLLMENT
(
  COURSE_ID INT(11) NOT NULL,
  STUD_ID INT(11) NOT NULL,
  PRIMARY KEY (COURSE_ID,STUD_ID),
  CONSTRAINT FK_ENROLLMENT_STUD FOREIGN KEY (STUD_ID) REFERENCES STUDENTS (STUD_ID),
  CONSTRAINT FK_ENROLLMENT_COURSE FOREIGN KEY (COURSE_ID) REFERENCES COURSES (COURSE_ID)
) ENGINE=INNODB DEFAULT CHARSET=LATIN1;

 

mybatis配置文件  

  对应的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="application.properties"/>
		
	  <typeAliases>
	  	<package name="com.yunzero.domain"/>
	  </typeAliases>
	  <typeHandlers>
	  	<typeHandler handler="com.yunzero.typehandlers.PhoneTypeHandler"/>
	  </typeHandlers>
	
	  <environments default="development">
	    <environment id="development">
	      <transactionManager type="JDBC"/>
	      <dataSource type="POOLED">
	        <property name="driver" value="${jdbc.driverClassName}"/>
	        <property name="url" value="${jdbc.url}"/>
	        <property name="username" value="${jdbc.username}"/>
	        <property name="password" value="${jdbc.password}"/>
	      </dataSource>
	    </environment>
	  </environments>
	  
	  <mappers>
	  	<package name="com.yunzero.mappers"/>
	  </mappers>
  	
</configuration>

  这个文件主要指定数据源和配置环境,另外也指定类型转换和对应的mapper接口。 

 

mapper接口和对应配置文件

  我们这里重点考察TutorMapper,它的对应的配置文件如下:

<?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.yunzero.mappers.TutorMapper">
  	
  	<resultMap type="Course" id="CourseResult">
  		<id 	column="course_id" property="courseId"/>
  		<result column="course_name" property="name"/>
  		<result column="description" property="description"/>
  		<result column="start_date" property="startDate"/>
  		<result column="end_date" property="endDate"/>
  	</resultMap>
  	
  	<resultMap type="Tutor" id="TutorResult">
  		<id 	column="tutor_id" property="tutorId"/>
  		<result column="tutor_name" property="name"/>
  		<result column="email" property="email"/>
  		<result column="phone" property="phone"/>
  		<association property="address" resultMap="com.yunzero.mappers.AddressMapper.AddressResult"/>
  		<collection property="courses"  resultMap="CourseResult"></collection>
  	</resultMap>
  	
</mapper>

  这里定义了两个映射的结果,一个是CourseResult,一个就是TutorResult。这里也引用了其他地方定义的ResultMap。  

  对应的TutorMapper接口定义如下:

public interface TutorMapper {
	@SelectProvider(type=TutorDynaSqlProvider.class, method="findAllTutorsSql")
	List<Tutor> findAllTutors();
	
	@SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByIdSql")
	Tutor findTutorById(int tutorId);
	
	@SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByNameAndEmailSql")
	Tutor findTutorByNameAndEmail(@Param("name")String name, @Param("email")String email);
	
	@InsertProvider(type=TutorDynaSqlProvider.class, method="insertTutor")
	@Options(useGeneratedKeys=true, keyProperty="tutorId")
	int insertTutor(Tutor tutor);
	
	@UpdateProvider(type=TutorDynaSqlProvider.class, method="updateTutor")
	int updateTutor(Tutor tutor);
	
	@DeleteProvider(type=TutorDynaSqlProvider.class, method="deleteTutor")
	int deleteTutor(int tutorId);
		
	@SelectProvider(type=TutorDynaSqlProvider.class, method="selectTutorById")
	@ResultMap("com.yunzero.mappers.TutorMapper.TutorResult")
	Tutor selectTutorById(int tutorId);
}

  这里有几个值得注意的地方。和前面简单的定义@Select, @Insert等方法不同,这里我们不是直接在这里写sql脚本。因为一方面除了前面提到的在字符串形式写的sql更加容易出错以外,在某些地方,我们需要结合程序逻辑和一些参数做一些更加复杂的运算。也就是动态sql。这些问题使得直接在xml或者annotation里写sql并不是一个很理想的选择。于是mybatis提供了一种比较好的写sql的方法,叫sqlprovider。它使得写sql脚本的代码显得更加面向对象,也显得好理解一点。

 

SQL provider 

  前面示例里使用到的sql provider类详细实现如下:

public class TutorDynaSqlProvider {
	public String findAllTutorsSql() {
		return new SQL() {{
			SELECT("tutor_id as tutorId, name, email");
		    FROM("tutors");
		  }}.toString();
	}

	public String findTutorByIdSql(final int tutorId) {
		return new SQL() {{
			SELECT("tutor_id as tutorId, name, email");
			FROM("TUTORS");
			WHERE("tutor_id = #{tutorId}"); // using placeholder #{tutorId} 
		}}.toString();
	}
	
	public String findTutorByNameAndEmailSql(Map<String, Object> map) {
		return new SQL() {{
			SELECT("tutor_id as tutorId, name, email");
		    FROM("tutors");
		    WHERE("name=#{name} AND email=#{email}");
		}}.toString();
	}
	
	public String insertTutor(final Tutor tutor) {
		return new SQL() {{
			INSERT_INTO("TUTORS");
		    if (tutor.getName() != null) {
		        VALUES("NAME", "#{name}");
		    }
		    
		    if (tutor.getEmail() != null) {
		        VALUES("EMAIL", "#{email}");
		    }
		}}.toString();
	}
	
	public String updateTutor(final Tutor tutor) {
		return new SQL() {{
			UPDATE("TUTORS");
		    
		    if (tutor.getName() != null) {
		    	SET("NAME = #{name}");
		    }
		    if (tutor.getEmail() != null) {
		    	SET("EMAIL = #{email}");
		    }
		    WHERE("TUTOR_ID = #{tutorId}");
		}}.toString();
	}
	
	public String deleteTutor(int tutorId) {
		
		return new SQL() {{
			DELETE_FROM("TUTORS");
		    WHERE("TUTOR_ID = #{tutorId}");
		}}.toString();
		
	}
	
	public String selectTutorById() {	
		return new SQL() {{
			SELECT("t.tutor_id, t.name as tutor_name, email");
			SELECT("a.addr_id, street, city, state, zip, country");
			SELECT("course_id, c.name as course_name, description, start_date, end_date");
			FROM("TUTORS t");
			LEFT_OUTER_JOIN("ADDRESSES a on t.addr_id=a.addr_id");
			LEFT_OUTER_JOIN("COURSES c on t.tutor_id=c.tutor_id");
			WHERE("t.TUTOR_ID = #{id}");
		}}.toString();
	}
}

   通过这部分的实现我们可以看到,我们可以将sql脚本,每个操作对应一个方法。然后在annotation里指定对应的类和方法。针对每个不同的实现,我们来看一下它们的细节。

 

SelectProvider

    SelectProvider里有几种情况,针对没有参数和有参数的情况。在没有参数的情况下,比如findAllTutorsSql,这里只需要在Mapper接口里指定对应的sqlprovider类和方法。在有一个参数的时候,比如有这个方法:

@SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByIdSql")
Tutor findTutorById(int tutorId);

  在这里mapper 接口的方法里其实是提供了一个参数的。但是sqlprovider方法里的实现如下:

public String findTutorByIdSql() {
	return new SQL() {{
		SELECT("tutor_id as tutorId, name, email");
		FROM("TUTORS");
		WHERE("tutor_id = #{tutorId}"); // using placeholder #{tutorId} 
	}}.toString();
}

    这里不是简单的做一个sql的拼接,注意到这里将mapper接口里的参数和占位符里的参数一一对应上了。当然,这里的findTutorByIdSql方法并没有带参数。

如果需要provider方法里的方法支持mapper接口里多个参数的话,需要做一些调整。比如mapper接口里有方法:

@SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByNameAndEmailSql")
Tutor findTutorByNameAndEmail(@Param("name")String name, @Param("email")String email);

  这里接口指定了两个@Param参数,它就对应sqlprovider提供的参数里需要映射过来的字段。而在这种情况下,sqlProvider对应的方法必须提供一个Map<String, Object>类型的参数。它对应的方法实现如下:

 

public String findTutorByNameAndEmailSql(Map<String, Object> map) {
	return new SQL() {{
		SELECT("tutor_id as tutorId, name, email");
	    FROM("TUTORS");
	    WHERE("name=#{name} AND email=#{email}");
	}}.toString();
}

除了selectprovider,其他的像insertProvider, deleteProvider, updateProvider则相对比较简单直观。它们有一个共同的特点就是所有的sqlprovider方法都必须返回String类型的值。另外,我们也注意到,有的方法里有一些判断的逻辑,比如

public String updateTutor(final Tutor tutor) {		
	return new SQL() {{
		UPDATE("TUTORS");
	    
	    if (tutor.getName() != null) {
	    	SET("NAME = #{name}");
	    }
	    
	    if (tutor.getEmail() != null) {
	    	SET("EMAIL = #{email}");
	    }
	    WHERE("TUTOR_ID = #{tutorId}");
	}}.toString();
}

  这种判断方式如果用纯sql的方式来实现会显得比较麻烦。但是这里用一种类似sql并结合java程序逻辑的方式实现了。它比对应的xml配置方式显得更加灵活。 

 

总结

  总的来说,结合xml, annotaion和sqlprovider的时候,需要注意几个点。一个是mapper的xml配置适合定义一些ResultMap,这样可以方便它们被其他的mapper接口重用。另外,annotation里适合指定对应的sqlprovider以及对应resultMap引用。而对于具体sql语句的编写,在sqlprovider里写则比较合适。而且,要特别注意sqlprovider里方法的参数和mapper接口里方法参数的对应。在这一块,目前感觉还不是很灵活。

 

参考材料

 java persistence with mybatis 3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值