Mybatis系列之实战篇(中)
接着《Mybatis系列之实战篇(上)》,我们继续。
数据表实体类
Province类
package com.emerson.etao.entity.base.address; /** * 省份实体类 * * @author Chris Mao(Zibing) * */ public class Province { private int provinceId; private String provinceName; public int getProvinceId() { return provinceId; } public void setProvinceId(int provinceId) { this.provinceId = provinceId; } public String getProvinceName() { return provinceName; } public void setProvinceName(String provinceName) { this.provinceName = provinceName; } @Override public String toString() { return "Province [provinceId=" + provinceId + ", provinceName=" + provinceName + "]"; } }
City类
package com.emerson.etao.entity.base.address; /** * 城市实体类 * * @author Chris Mao(Zibing) * */ public class City { private int cityId; private String cityName; public int getCityId() { return cityId; } public void setCityId(int cityId) { this.cityId = cityId; } public String getCityName() { return cityName; } public void setCityName(String cityName) { this.cityName = cityName; } @Override public String toString() { return "City [cityId=" + cityId + ", cityName=" + cityName + "]"; } }
Area类
package com.emerson.etao.entity.base.address; /** * 区域实体类 * * @author Chris Mao(Zibing) * */ public class Area { private int areaId; private String areaName; public int getAreaId() { return areaId; } public void setAreaId(int areaId) { this.areaId = areaId; } public String getAreaName() { return areaName; } public void setAreaName(String areaName) { this.areaName = areaName; } @Override public String toString() { return "Area [areaId=" + areaId + ", areaName=" + areaName + "]"; } }
Purpose类
package com.emerson.etao.entity.base.address; /** * 地址用途实体类 * * @author Chris Mao(Zibing) * */ public class Purpose { public static final int PURPOSE_LEGAL = 1; public static final int PURPOSE_BILLTO = 2; public static final int PURPOSE_SHIPTO = 3; public static final int PURPOSE_DUNNING = 4; private int purposeId; private String purposeName; public int getPurposeId() { return purposeId; } public void setPurposeId(int purposeId) { this.purposeId = purposeId; } public String getPurposeName() { return purposeName; } public void setPurposeName(String purposeName) { this.purposeName = purposeName; } @Override public String toString() { return "Purpose [purposeId=" + purposeId + ", purposeName=" + purposeName + "]"; } }
Address类
package com.emerson.etao.entity.base.address; import com.emerson.etao.entity.BaseEntity; /** * 地址实体类 * * @author Chris Mao(Zibing) * */ public class Address extends BaseEntity { private int addressId; private Province province; private City city; private Area area; private String street; private String zipCode; private String contactPerson; private String tel; private String fax; private String cellPhone; private String email; public int getAddressId() { return addressId; } public void setAddressId(int addressId) { this.addressId = addressId; } public Province getProvince() { return province; } public void setProvince(Province province) { this.province = province; } public City getCity() { return city; } public void setCity(City city) { this.city = city; } public Area getArea() { return area; } public void setArea(Area area) { this.area = area; } public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getZipCode() { return zipCode; } public void setZipCode(String zipCode) { this.zipCode = zipCode; } public String getContactPerson() { return contactPerson; } public void setContactPerson(String contactPerson) { this.contactPerson = contactPerson; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public String getFax() { return fax; } public void setFax(String fax) { this.fax = fax; } public String getCellPhone() { return cellPhone; } public void setCellPhone(String cellPhone) { this.cellPhone = cellPhone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String toDisplayString() { String result = province.getProvinceName() + ", " + city.getCityName() + ", " + area.getAreaName() + ", " + this.street + ", " + this.zipCode + ", " + this.contactPerson; if ((this.tel != null) && !("".equals(this.tel))) { result += ", " + this.tel; } else if ((this.cellPhone != null) && !("".equals(this.cellPhone))) { result += ", " + this.cellPhone; } return result; } @Override public String toString() { return "Address [addressId=" + addressId + ", province=" + province + ", city=" + city + ", area=" + area + ", street=" + street + ", zipCode=" + zipCode + ", contactPerson=" + contactPerson + ", tel=" + tel + ", fax=" + fax + ", cellPhone=" + cellPhone + ", email=" + email + ", isValid=" + isValid + ", createdTime=" + createdTime + ", updateTime=" + updateTime + "]"; } }
Party类
package com.emerson.etao.entity.base.customer; import com.emerson.etao.entity.BaseEntity; /** * 客户Party实体类 * * @author Chris Mao(Zibing) * */ public class Party extends BaseEntity { private int partyId; private String partyName; private String country; private String area; public int getPartyId() { return partyId; } public void setPartyId(int partyId) { this.partyId = partyId; } public String getPartyName() { return partyName; } public void setPartyName(String partyName) { this.partyName = partyName; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getArea() { return area; } public void setArea(String area) { this.area = area; } @Override public String toString() { return "Party [partyId=" + partyId + ", partyName=" + partyName + ", country=" + country + ", area=" + area + ", isValid=" + isValid + ", createdTime=" + createdTime + ", updateTime=" + updateTime + "]"; } }
Code类
package com.emerson.etao.entity.base.customer; import com.emerson.etao.entity.BaseEntity; import com.emerson.etao.entity.base.Plant; /** * 客户代码实体类 * * @author Chris Mao(Zibing) * */ public class Code extends BaseEntity { private int codeId; private String customerCode; private Party party; private Plant plant; public int getCodeId() { return codeId; } public void setCodeId(int codeId) { this.codeId = codeId; } public String getCustomerCode() { return customerCode; } public void setCustomerCode(String customerCode) { this.customerCode = customerCode; } public Plant getPlant() { return plant; } public void setPlant(Plant plant) { this.plant = plant; } public Party getParty() { return party; } public void setParty(Party party) { this.party = party; } @Override public String toString() { return "Code [codeId=" + codeId + ", customerCode=" + customerCode + ", party=" + party + ", plant=" + plant + ", isValid=" + isValid + ", createdTime=" + createdTime + ", updateTime=" + updateTime + "]"; } }
Location类
package com.emerson.etao.entity.base.customer; import com.emerson.etao.entity.BaseEntity; import com.emerson.etao.entity.base.address.Address; import com.emerson.etao.entity.base.address.Purpose; /** * 客户位置实体类 * * 地址 + 用途,构成客户位置 * * @author Chris Mao(Zibing) * */ public class Location extends BaseEntity { private int locationId; private int codeId; private Address address; private Purpose purpose; public int getLocationId() { return locationId; } public void setLocationId(int locationId) { this.locationId = locationId; } public int getCodeId() { return codeId; } public void setCodeId(int codeId) { this.codeId = codeId; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public Purpose getPurpose() { return purpose; } public void setPurpose(Purpose purpose) { this.purpose = purpose; } public String toDisplayString() { return this.purpose.getPurposeName() + ": " + this.address.toDisplayString(); } @Override public String toString() { return "Location [locationId=" + locationId + ", address=" + address + ", purpose=" + purpose + "]"; } }
以上这些都准备好了,下面可以开始着手写Mybatis映射文件了。我们自底向上,从地址开始写起。
Address类Dao接口、Service接口、映射文件及单元测试
Dao接口
Address作为基础类,不仅要实现基类(参见《Mybatis系列之实战篇(上)》中“Mybatisy接口及映射文件”)中约定的方法,还需要额外定义了三个方法,分别用于获取省、市、区的列表。
package com.emerson.etao.dao.base.address; import java.util.List; import com.emerson.etao.dao.IBaseDao; import com.emerson.etao.entity.base.address.Address; import com.emerson.etao.entity.base.address.Area; import com.emerson.etao.entity.base.address.City; import com.emerson.etao.entity.base.address.Province; /** * 地址DAO接口 * * @author Chris Mao(Zibing) * */ public interface IAddressDao extends IBaseDao { /** * 获取省份列表 * * @return */ public ListgetProvinceList(); /** * 获取指定省份的城市列表 * * @param provinceId * @return */ public List getCityList(int provinceId); /** * 获取指定城市的地区列表 * * @param cityId * @return */ public List getAreaList(int cityId); }
映射文件
由于我们使用了接口式编程,所以映射文件中的名称空间名称必须和我们定义的接口全限定名一致!
这里着重讲解一下id为find的select元素的写法。这是一个查询方法,把查询条件封装成一个实体类对象,然后根据其属性值,动态生成SQL查询语句。Address对象中又嵌套有Province / City / Arae三个子对象,所以要创建查询语句时, 在引用其属性值之前,一定要判断其是否为null值,否则有可能会得到空引用的错误。address_id, province_id, province_name, city_id, city_name, area_id, street, zip_code, contact_person, tel, fax, cell_phone, email, is_valid, created_time, update_time province_id, city_id, area_id, street, zip_code, contact_person, tel, fax, cell_phone, email, is_valid, created_time INSERT INTO address( ) VALUES(#{province.provinceId}, #{city.cityId}, #{area.areaId}, #{street}, #{zipCode}, #{contactPerson}, #{tel}, #{fax}, #{cellPhone}, #{email}, #{isValid}, #{createdTime}) INSERT INTO address( ) VALUES (#{addr.province.provinceId}, #{addr.city.cityId}, #{addr.area.areaId}, #{addr.street}, #{addr.zip_code}, #{addr.contactPerson}, #{addr.tel}, #{addr.fax}, #{addr.cellPhone}, #{addr.email}, #{addr.isValid}, #{addr.createdTime}) UPDATE address WHERE address_id = #{addressId} province_id = #{province.provinceId}, city_id = #{city.cityId}, area_id = #{area.areaId}, street = #{street}, zip_code = #{zipCode}, contact_person = #{contactPerson}, tel = #{tel}, fax = #{fax}, cell_phone = #{cellPhone}, email = #{email} UPDATE address SET is_valid = 0 WHERE address_id = #{id} UPDATE address SET is_valid = 0 WHERE address_id in #{item}
在映射文件中除了用到了select / insert / update / delete 元素,还使用到了创建动态SQL的where / set / if元素,以及foreach,小伙伴可以慢慢体会这些元素的使用方法。
Service接口及实现
接口定义。
package com.emerson.etao.service.base.address; import java.util.List; import com.emerson.etao.entity.base.address.Address; import com.emerson.etao.entity.base.address.Area; import com.emerson.etao.entity.base.address.City; import com.emerson.etao.entity.base.address.Province; import com.emerson.etao.service.IBaseService; /** * 客户地址服务层接口 * * @author Chris Mao(Zibing) * */ public interface IAddressService extends IBaseService { /** * 获取省份列表 * * @return */ public ListgetProvinceList(); /** * 获取指定省份的城市列表 * * @param provinceId * @return */ public List getCityList(int provinceId); /** * 获取指定城市的地区列表 * * @param cityId * @return */ public List getAreaList(int cityId); }
接口实现,这里需要在接口实现类的定义处使用Service注解将其定义为一个J2EE组件。
package com.emerson.etao.service.impl.base.address; import java.util.List; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.emerson.etao.dao.IBaseDao; import com.emerson.etao.dao.base.address.IAddressDao; import com.emerson.etao.entity.base.address.Address; import com.emerson.etao.entity.base.address.Area; import com.emerson.etao.entity.base.address.City; import com.emerson.etao.entity.base.address.Province; import com.emerson.etao.service.base.address.IAddressService; import com.emerson.etao.service.impl.BaseServiceImpl; /** * 地址服务接口 * * @author Chris Mao(Zibing) * */ @Service("addressService") public class AddressServiceImpl extends BaseServiceImpl implements IAddressService { @Resource private IAddressDao dao; @Override protected IBaseDao getBaseDao() { return this.dao; } @Override public ListgetProvinceList() { return this.dao.getProvinceList(); } @Override public List getCityList(int provinceId) { return this.dao.getCityList(provinceId); } @Override public List getAreaList(int cityId) { return this.dao.getAreaList(cityId); } }
单元测试
这些都写好之后,就可以对我们写的代码进行单元测试了。我们一定要培养自己有写单元测试的习惯,这样不仅可以确保自己的代码质量,即便在将来我们对代码进行重构或是增加功能时,这些测试代码仍可以复用,确保我们不会因为重构或是增加新的功能引入新的错误。通过了单元测试并不能说明我们的程序完全没有作何问题。单元测试只能确保我们的程序代码没有技术错误,无法保证没有业务错误。所以后继我们还会对程序进行功能测试,来暴露出程序上的功能错误。
package com.emerson.etao.service.address; import static org.junit.Assert.*; import java.util.List; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.emerson.etao.entity.base.address.*; import com.emerson.etao.service.base.address.IAddressService; /** * 地址服务测试类 * * @author Chris Mao(Zibing) * */ public class AddressServiceTest { private static IAddressService addressService; @BeforeClass public static void setUpBeforeClass() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("root-context-test.xml"); addressService = (IAddressService) ac.getBean("addressService"); } @AfterClass public static void tearDownAfterClass() throws Exception { addressService = null; } @Test public void testGetById() { System.out.println("testGetById start..."); Address address = addressService.getById(1); assertNotNull(address); System.out.println("testGetById end..."); } @Test public void testGetAll() { System.out.println("testGetAll start..."); List list = addressService.getAll(0); assertNotNull(list); System.out.println("testGetAll end..."); } @Test public void testFind() { System.out.println("testFind start..."); Address address = new Address(); address.setZipCode("433"); List list = addressService.find(address); assertEquals(1, list.size()); address.setZipCode(null); address.setProvince(new Province()); address.getProvince().setProvinceName("上海"); list = addressService.find(address); assertEquals(1, list.size()); System.out.println("testFind end..."); } @Test public void testInsert() { System.out.println("testInsert start..."); Address address = new Address(); Province province = new Province(); City city = new City(); Area area = new Area(); province.setProvinceId(2); city.setCityId(2); area.setAreaId(26); address.setProvince(province); address.setCity(city); address.setArea(area); address.setStreet("工艺品公司进出口仓库8号库军工路1300"); address.setZipCode("200433"); address.setContactPerson("陈伟民"); address.setTel("021-65797887"); address.setFax(null); address.setCellPhone(null); addressService.insert(address); System.out.println("testInsert end..."); } @Test public void testBatchInsert() { System.out.println("testBatchInsert start..."); System.out.println("testBatchInsert end..."); } @Test public void testUpdate() { System.out.println("testUpdate start..."); Address address = addressService.getById(1); address.setEmail("[email protected]"); int effectedRows = addressService.update(address); assertEquals(1, effectedRows); System.out.println("testUpdate end..."); } @Test public void testDelete() { System.out.println("testDelete start..."); int effectedRows = addressService.delete(1); assertEquals(1, effectedRows); System.out.println("testDelete end..."); } @Test public void testBatchDelete() { System.out.println("testBatchDelete start..."); System.out.println("testBatchDelete end..."); } @Test public void testGetProvinceList() { System.out.println("testGetProvinceList start..."); Listlist = addressService.getProvinceList(); assertEquals(34, list.size()); System.out.println("testGetProvinceList end..."); } @Test public void testGetCityList() { System.out.println("testGetCityList start..."); List list = addressService.getCityList(12); // Jiangsu assertEquals(13, list.size()); System.out.println("testGetCityList end..."); } @Test public void testGetAreaList() { System.out.println("testGetAreaList start..."); List list = addressService.getAreaList(113); // Suzhou assertEquals(13, list.size()); System.out.println("testGetAreaList end..."); } }
Plant / Party / Code其实现步骤和Address类一样,也是要经过定义Dao接口、编写映射文件、定义服务层接口并实现其功能,以及单元测试这样四步。本文重点放在了映射文件的编写上,所以,这里就仅贴出其他类的映射文件内容,有兴趣的小伙伴也可以试着自己完成接口部分和测试部分的编写。
Plant映射文件
plant_id, plant_code, plant_name, is_valid, created_time, update_time plant_code, plant_name, is_valid, created_time INSERT INTO plant( ) VALUES(#{plantCode}, #{plantName}, #{isValid}, #{createdTime}) INSERT INTO plant( ) VALUES (#{p.plantCode}, #{p.plantName}, #{p.isValid}, #{p.createdTime}) UPDATE plant WHERE plant_id = #{plantId} UPDATE plant SET is_valid = 0 WHERE plant_id = #{id} UPDATE plant SET is_valid = 0 WHERE plant_id in #{item}
Party映射文件
party_id, party_name, country, area, is_valid, created_time, update_time party_name, country, area, is_valid, created_time INSERT INTO customer_party( ) VALUES(#{partyName}, #{country}, #{area}, #{isValid}, #{createdTime}) INSERT INTO customer_party( ) VALUES (#{party.partyName}, #{party.country}, #{party.area}, #{party.isValid}, #{party.createdTime}) UPDATE customer_party WHERE party_id = #{partyId} party_name = #{partyName}, country = #{country}, area = #{area}, is_valid = #{isValid}, UPDATE customer_party SET is_valid = 0 WHERE party_id = #{id} UPDATE customer_party SET is_valid = 0 WHERE party_id in #{item}
Code映射文件
code_id ,customer_code, plant_id, party_id, is_valid, created_time, update_time, plant_code, plant_name, plant_is_valid, plant_created_time, plant_update_time, party_name, country, area, party_is_valid, party_created_time, party_update_time customer_code, plant_id, party_id, is_valid, created_time INSERT INTO customer_code( ) VALUES(#{customerCode}, #{plant.plantId}, #{party.partyId}, #{isValid}, #{createdTime}) INSERT INTO customer_code( ) VALUES (#{c.customerCode}, #{c.plant.plantId}, #{c.party.partyId}, #{c.isValid}, #{c.createdTime}) INSERT INTO customer_location (code_id, address_id, purpose_id, is_valid, created_time) VALUES(#{codeId}, #{address.addressId}, #{purpose.purposeId}, #{isValid}, #{createdTime}) UPDATE customer_code WHERE code_id = #{codeId} plant_id = #{plant.plantId}, party_id = #{party.partyId}, UPDATE customer_code SET is_valid = 0 WHERE code_id = #{id} UPDATE customer_code SET is_valid = 0 WHERE id in #{item} DELETE FROM customer_location WHERE location_id = #{id}
至此,这个示例的代码部分已全部讲解完毕。在《Mybatis系列这实战篇(下)》中,我会对开发中需要注意的问题点进行总结。
附录
《Mybatis系列(一)入门》
《Mybatis系列(二)配置》
《Mybatis系列(三)简单示例》
《Mybatis系列(四)映射文件》
《ybatis系列(五)动态SQL》
《Mybatis系列(六)接口式编程》
《Mybatis系列(七)关联映射》
《Mybatis系列(八)集合映射》
《Mybatis系列(九)Spring & Mybatis整合》