通用Mapper实现查询总记录数
实现步骤:
0.引入通用Mapper的jar包
Pom.xml代码:
<!-- 通用Mapper,所有的单表的代码都不用编写 -->
<dependency>
<groupId>com.github.abel533</groupId>
<artifactId>mapper</artifactId>
<version>2.3.2</version>
</dependency>
- 在Mybatis核心配置文件里,配置mapperInterceptor插件
- 写一个泛型接口,并让其他所有mapper接口继承这个泛型接口
- 写一个类,继承MapperTemplate模板
- 开发具体方法
- 在泛型接口里,利用@XXXProvider注解,调用类里的方法
- 测试
一、查询数据库总记录数
SysMapperProvider.class代码:
public class SysMapperProvider extends MapperTemplate{
public SysMapperProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
System.out.println(mapperClass.getName());
}
public SqlNode selectCount(MappedStatement ms){
/*
* 1.ms.getId()得到的是namespace.id;
* 比如:当ModuleMapper接口执行 父接口里的 selectCount()方法时,mybatis会通过@SelectProvider
* 形成如下的结构:
* namepace=cn.tarena.ht.mapper.ModuleMapper
* <select id="selectCount" resultType="待设置">
* sql语句
* </select>
* 2.所以此时,ms.getId()=cn.tarena.ht.mapper.ModuleMapper.selectCount
* 3.我们的目的是动态的获取表名,而这个表名我们是在实体类上,加@Talbe(name="表名")来做的,所以最终的目的就是获取Module这个实体类
* 4.获取实体类的步骤如下:
* ①把cn.tarena.ht.mapper.ModuleMapper.selectCount截串,截出cn.tarena.ht.mapper.ModuleMapper,得到ModuleMapper接口
* ②得到ModuleMapper接口之后,可以获得它的父接口,SysMapper<Module>
* ③得到父接口后,因为这个接口含有泛型,所以可以把这个父接口转换为泛型类型,即ParameterizedType
* ④得到ParameterizedType后,就可以获得泛型里的实体type,强转成通配类型的class<?>
* ⑤有了class<?>之后,就能获得class上的指定类型的注解
* ⑥通过@Table注解的name属性,就可以获得表名
* 这样,表名就可以动态获得了
*
*/
String namespaceId=ms.getId();//cn.tarena.ht.mapper.ModuleMapper.selectCount
String interfaceClassName=namespaceId.substring(0, namespaceId.lastIndexOf("."));
String tableName=null;
try {
Class<?> interfaceClass=Class.forName(interfaceClassName);
Type[] types=interfaceClass.getGenericInterfaces();
Type t=types[0];
if(t instanceof ParameterizedType){
ParameterizedType superInterfaceType=(ParameterizedType) t;
Type[] entityClasses=superInterfaceType.getActualTypeArguments();
Class<?> entityClass=(Class<?>) entityClasses[0];
tableName=entityClass.getAnnotation(Table.class).name();
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String text="select count(*) from "+tableName;
SqlNode sqlNode=new StaticTextSqlNode(text);
return sqlNode;
}
}
SysMapper接口代码:
public interface SysMapper<T> {
@SelectProvider(type=SysMapperProvider.class,method="dynamicSQL")
public int selectCount();
}
ModuleMapper接口代码:
public interface ModuleMapper extends SysMapper<Module>{
}
Module(pojo)代码:
@Table(name="dept_p")
public class Module extends BasePojo{
}
通用Mapper实现查询所有模块信息
SysMapperProvider代码:
public SqlNode select(MappedStatement ms){
//得到父接口里泛型的实体类
Class<?> entityClass=this.getSelectReturnType(ms);
//设置返回值类型
this.setResultType(ms, entityClass);
//得到表名
String tableName=entityClass.getAnnotation(Table.class).name();
String text="select * from "+tableName;
SqlNode sqlNode=new StaticTextSqlNode(text);
return sqlNode;
}
SysMapper代码:
@SelectProvider(type=SysMapperProvider.class,method="dynamicSQL")
public List<T> select();
商品新增—商品分类树展现
ItemCatController代码:
@Controller
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
/*商品分类树展示要求的json格式
* easyui树要求的json格式:
* {"id":2,"text":"商品名",state:"closed"}state的属性如果是closed,表示这个是父节点,它还有子节点。open代表子节点
* 商品分类树实现思路:
* 商品分类树,最开始只展示第一级商品分类,然后,当点击某个一级商品分类时,前台会把这个一级商品分类的id传过来,根据这个id,去查询它的二级子分类
* 然后把二级子分类展现出来,也就是说,当你点击某一个父节点时,它才会去加载对应的数据,不会一起把所有数据都查出来,这样能够避免出现过大的数据量在网络中进行传输
* 当点击某个二级子分类时,会根据这个二级分类的id,去查询对应的第三级分类
*
* 对应的前台代码在web-app/js/common.js的99行到140行
*/
@RequestMapping("/item/cat/list")
@ResponseBody
public List<ItemCat> list(@RequestParam(value="id",defaultValue="0")Long parentId){
return itemCatService.findItemCatByParentId(parentId);
}
}
EasyUI树的JSON格式说明
initItemCat : function(data){
$(".selectItemCat").each(function(i,e){//i= index 下标,e:element:元素
var _ele = $(e);
if(data && data.cid){
_ele.after("<span style='margin-left:10px;'>"+data.cid+"</span>");
}else{
_ele.after("<span style='margin-left:10px;'></span>");
}
_ele.unbind('click').click(function(){
$("<div>").css({padding:"5px"}).html("<ul>")
.window({
width:'500',
height:"450",
modal:true,
closed:true,
iconCls:'icon-save',
title:'选择类目',
onOpen : function(){ //当窗口打开后执行
var _win = this;
$("ul",_win).tree({
url:'/item/cat/list',
animate:true,
onClick : function(node){
if($(this).tree("isLeaf",node.target)){
// 填写到cid中
_ele.parent().find("[name=cid]").val(node.id);
_ele.next().text(node.text).attr("cid",node.id);
$(_win).window('close');
if(data && data.fun){
data.fun.call(this,node);
}
}
}
});
},
onClose : function(){
$(this).window("destroy");
}
}).window('open');
});
});
},
后台组织Json数据的Controller代码:
@RequestMapping("/item/cat/list")
public void list(HttpServletResponse res){
System.out.println("收到前台Ajax请求");
// [{ "id":1,"text":"parent","state":"closed"}]
String json="[{\"id\":1,\"text\":\"parent\",\"state\":\"closed\"}]";
try {
res.getWriter().write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
第二种方法:通过java对象+SpringMVC的特定注解,完成json数据的转换
pojo代码:
public class ItemCat {
private long id;
private String text;
private String state;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
controller代码:
@RequestMapping("/item/cat/list")
@ResponseBody
public List<ItemCat> list(){
System.out.println("收到前台Ajax请求");
ItemCat item=new ItemCat();
item.setId(1);
item.setText("javaSpringMVC");
item.setState("closed");
List<ItemCat> list=new ArrayList<>();
list.add(item);
return list;
}
知识点:
1.@ResponseBody注解:这个注解可以将对象或者对象集合转成json串格式,然后底层调用了HttpServletResponse.Write(Json)方法,将json串写到响应正文中。
2.使用@ResponseBody注解,返回值必须是对象类型或者是对象集合。
这里有个坑:
本例中,只封装了一个ItemCat对象的信息,有的同学问,返回值类型为什么不是ItemCat,而是List。这是因为如果返回的是单个对象,@ResponseBody解析的Json串是这样的:
{"id":1,"text":"javaSpringMVC","state":"closed"}
注意,没有了两边的 [ ] ,这样就不符合EasyUi树数据的结构,所以就显示不出来了。
根据前面的基础,完成京淘商品的分类查询树
持久类ItemCat代码:
@Table(name="tb_item_cat")
public class ItemCat extends BasePojo {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY) //自增主键
private long id;
private String name;
private long parentId;
private int status;
private int sortOrder;
private boolean isParent;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getParentId() {
return parentId;
}
public void setParentId(long parentId) {
this.parentId = parentId;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public int getSortOrder() {
return sortOrder;
}
public void setSortOrder(int sortOrder) {
this.sortOrder = sortOrder;
}
public boolean isParent() {
return isParent;
}
public void setParent(boolean isParent) {
this.isParent = isParent;
}
public String getText(){
return name;
}
public String getState(){
return isParent?"closed":"open";
}
}
注意:
因为EasyUI树数据要求的节点属性名为text,所以要加上getText()方法,以满足数据格式需要,此外就是需要额外加上getState()方法,来控制根节点或叶节点。
<resultMap type="ItemCat" id="itemCatRM">
<id property="id" column="id"/>
<result property="parentId" column="parent_id"/>
<result property="name" column="name"/>
<result property="sortOrder" column="sort_order"/>
<result property="isParent" column="is_parent"/>
<result property="status" column="status"/>
<result property="created" column="created"/>
<result property="updated" column="updated"/>
</resultMap>
<select id="findByParentId" parameterType="long" resultMap="itemCatRM">
select * from tb_item_cat where parent_id=#{id}
</select>
ItemCatMapper.xml代码:
知识点:
传参类型可以是long型
ItemCatMapper接口类代码:
public interface ItemCatMapper extends SysMapper<ItemCat> {
public List<ItemCat> findByParentId(long id);
}
ItemCatservice代码:
@Service
public class ItemCatService {
@Autowired
private ItemCatMapper itemCatMapper;
public List<ItemCat> findByParentId(Long id) {
return itemCatMapper.findByParentId(id);
}
}
ItemCatController代码:
@Controller
public class ItemCatController {
@Autowired
private ItemCatService itemCatService;
@RequestMapping("/item/cat/list")
@ResponseBody
public List<ItemCat> list(@RequestParam(defaultValue="0")Long id){
System.out.println(id);
List<ItemCat> list=itemCatService.findByParentId(id);
System.out.println(list.size());
return list;
}
}
代码实现思路:
- 形参里有一个id值,当第一次点击选择类目时,这时候我们没有做任何的选中操作,所以传过来的id值是null,但是我们可以恰好利用这一点,把初始化默认值设为0(利用注解),因为我们最开始要展示的肯定是第一级商品分类,所以当第一次点击’选择类目‘时,我们可以这个id=0作为一级商品分类的parent_id来查询(因为第一级分类就是根,没有父分类)。
- 第一级分类树数据都查出来之后,当点击某一个一级商品分类时,此时,这个一级商品分类的id就传给后台了,我们可以拿这个一级商品的id作为二级商品的parent_id来查询,继而实现异步加载某一级商品分类树
商品新增保存+商品描述保存+@GeneratedValue
ItemController代码:
/*
* 保存方法的前台对应的代码在 web-app/WEB-INF/views/item-add.jsp的第76-110行
* 注意前台页面<input name的属性名和item对象的私有属性名一致即可
* 此外,需要注意的是,商品描述属于大字段,一般单独建一张表来存储,数据库对应的表是:tb_item_desc
*/
@RequestMapping("/item/save")
@ResponseBody
public SysResult save(Item item,String desc){
return itemService.save(item,desc);
}
itemService代码:
public SysResult save(Item item, String desc) {
try {
item.setStatus(1);//设置默认状态
item.setCreated(new Date());
item.setUpdated(item.getCreated());
itemMapper.insert(item);
//因为item的id是自增的,虽然已经调用了insert方法,但是此时拿item的id值是拿不到的,
//所以,要在Item类里的id属性上,用一个特定的注解,让自增生成的id值回写回来,才能拿到
//这个特定的注解是:@GeneratedValue(strategy=GenerationType.IDENTITY)
ItemDesc itemDesc=new ItemDesc();
itemDesc.setItemId(item.getId());
itemDesc.setItemDesc(desc);
itemDesc.setCreated(item.getCreated());
itemDesc.setUpdated(item.getUpdated());
itemDescMapper.insert(itemDesc);
//新增操作成功或失败,要告诉前台,这里也要组织对应的json格式
//如果成功,则返回{"status":200,"msg":"OK","data":"null","ok":true}
//我们已经为Json格式写好了一个对象,SysResult 如果成功,直接调用SysResult.ok()方法即可
return SysResult.ok();
} catch (Exception e) {
return SysResult.build(201, e.getMessage());
}
注意,这里我们返回的SysResult对象,因为前台的Ajax请求有一个状态码的校验,status==200,
{"status":200,"msg":"OK","data":null,"ok":true}
提示商品新增成功,所以这里我们也需要组织对应的json数据。我们用了一个SysResult对象做了封装,达到复用,以后只要是需要给前台返回Ajax的状态标识,都可以用这个对象来做。
通用类SysResult代码-略:
商品编辑
前台代码,当在item-list.jsp页面点击“编辑”按钮时触发的js代码:
{
text:'编辑',
iconCls:'icon-edit',
handler:function(){
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','必须选择一个商品才能编辑!');
return ;
}
if(ids.indexOf(',') > 0){
$.messager.alert('提示','只能选择一个商品!');
return ;
}
$("#itemEditWindow").window({
onLoad :function(){
//回显数据
var data = $("#itemList").datagrid("getSelections")[0];
data.priceView = KindEditorUtil.formatPrice(data.price);
$("#itemeEditForm").form("load",data);
// 加载商品描述
$.getJSON('/item/query/item/desc/'+data.id,function(_data){
if(_data.status == 200){
//UM.getEditor('itemeEditDescEditor').setContent(_data.data.itemDesc, false);
itemEditEditor.html(_data.data.itemDesc);
}
});
//加载商品规格
$.getJSON('/item/param/item/query/'+data.id,function(_data){
if(_data && _data.status == 200 && _data.data && _data.data.paramData){
$("#itemeEditForm .params").show();
$("#itemeEditForm [name=itemParams]").val(_data.data.paramData);
$("#itemeEditForm [name=itemParamId]").val(_data.data.id);
//回显商品规格
var paramData = JSON.parse(_data.data.paramData);
var html = "<ul>";
for(var i in paramData){
var pd = paramData[i];
html+="<li><table>";
html+="<tr><td colspan=\"2\" class=\"group\">"+pd.group+"</td></tr>";
for(var j in pd.params){
var ps = pd.params[j];
html+="<tr><td class=\"param\"><span>"+ps.k+"</span>: </td><td><input autocomplete=\"off\" type=\"text\" value='"+ps.v+"'/></td></tr>";
}
html+="</li></table>";
}
html+= "</ul>";
$("#itemeEditForm .params td").eq(1).html(html);
}
});
KindEditorUtil.init({
"pics" : data.image,
"cid" : data.cid,
fun:function(node){
KindEditorUtil.changeItemParam(node, "itemeEditForm");
}
});
}
}).window("open");
}
}
前台代码:
$.post("/item/update",$("#itemeEditForm").serialize(), function(data){
if(data.status == 200){
$.messager.alert('提示','修改商品成功!','info',function(){
$("#itemEditWindow").window('close');
$("#itemList").datagrid("reload");
});
}
});
ItemController代码:
/*
* 更新的前台代码在web-app/WEB-INF/views/item-edit.jsp 第67行到第111行
*/
@RequestMapping("/item/update")
@ResponseBody
public SysResult update(Item item,String desc){
return itemService.update(item,desc);
}
ItemService代码:
public void update(Item item)throws Exception {
item.setStatus(1);
item.setUpdated(new Date());
itemMapper.updateByPrimaryKeySelective(item);
}
商品删除
前台item-list.jsp中的代码:
{
text:'删除',
iconCls:'icon-cancel',
handler:function(){
var ids = getSelectionsIds();
if(ids.length == 0){
$.messager.alert('提示','未选中商品!');
return ;
}
$.messager.confirm('确认','确定删除ID为 '+ids+' 的商品吗?',function(r){
if (r){
var params = {"ids":ids};
$.post("/item/delete",params, function(data){
if(data.status == 200){
$.messager.alert('提示','删除商品成功!',undefined,function(){
$("#itemList").datagrid("reload");
});
}
});
}
});
}
ItemController代码:
@RequestMapping("/item/delete")
@ResponseBody
public SysResult itemDelete(String[] ids){
itemService.delete(ids);
return SysResult.ok();
}
ItemService代码:
public void delete(String[] ids) {
itemMapper.deleteByIDS(ids);
}
懒加载思想
懒加载,在京淘项目的商品分类查询中,
并没有一次把全部数据查询出来,而是比如:当点击某一个一级商品分类时,才去查对应的二级商品分类,不点击,就不查。这就像一个比较懒的人,打他一下他才工作,不打就不工作。
懒加载的好处:
每次加载一小部分的数据,数据小,所以网络传输的时间短,响应速度快。这样能避免一次加载全部数据带来的问题,比如数据量过大,占用过多的内容,以及占用更大的带宽传输
懒加载的坏处:
懒加载会造成多次访问数据库